This notebooks explores the results from running cell type annotation
with SingleR using the NBAtlas. The NBAtlas reference was
aggregated with SingleR prior to model training.
In this notebook, we visualize inferred cell type annotations
directly, compare them to normal consensus cell types, and validate cell
type assignments with marker genes.
Setup
options(readr.show_col_types = FALSE)
suppressPackageStartupMessages({
library(ggplot2)
library(patchwork)
library(SingleCellExperiment)
})
theme_set(theme_bw())
# Define color ramp for shared use in the heatmaps
heatmap_col_fun <- circlize::colorRamp2(c(0, 1), colors = c("white", "darkslateblue"))
# Set heatmap padding option
ComplexHeatmap::ht_opt(TITLE_PADDING = grid::unit(0.6, "in"))
# settings
options(
dplyr.summarise.inform = FALSE,
readr.show_col_types = FALSE
)
Paths
# The base path for the OpenScPCA repository, found by its (hidden) .git directory
repository_base <- rprojroot::find_root(rprojroot::is_git_root)
module_dir <- file.path(repository_base, "analyses", "cell-type-neuroblastoma-04")
ref_dir <- file.path(module_dir, "references")
results_dir <- file.path(module_dir, "results", "singler")
data_dir <- file.path(repository_base, "data", "current", "SCPCP000004")
merged_dir <- file.path(repository_base, "data", "current", "results", "merge-sce", "SCPCP000004")
# SingleR files
singler_files <- list.files(
path = results_dir,
pattern = "_singler-annotations\\.tsv",
recursive = TRUE,
full.names = TRUE
) |>
# add names as library id
purrr::set_names(
\(x) {
stringr::str_remove(basename(x), "_singler-annotations.tsv")
}
)
# merged SCE file
sce_file <- file.path(
merged_dir,
"SCPCP000004_merged.rds"
)
# broad consensus cell type groups
validation_url <- "https://raw.githubusercontent.com/AlexsLemonade/OpenScPCA-analysis/refs/heads/main/analyses/cell-type-consensus/references/consensus-validation-groups.tsv"
# palette files
recoded_palette_file <- file.path(
module_dir,
"palettes",
"harmonized-cell-type-palette.tsv"
)
nbatlas_palette_file <- file.path(
module_dir,
"palettes",
"nbatlas-marker-genes-palette.tsv"
)
# marker genes for validation
consensus_markers_file <- "https://raw.githubusercontent.com/AlexsLemonade/OpenScPCA-analysis/refs/heads/main/analyses/cell-type-consensus/references/validation-markers.tsv"
nbatlas_markers_file <- file.path(ref_dir, "nbatlas-marker-genes.tsv")
Functions
# Source Jaccard and heatmap utilities functions
source(file.path(module_dir, "scripts", "utils", "jaccard-utils.R"))
# Source additional utilities functions:
# - select_nbatlas_markers()
# - harmonize_celltypes()
# - faceted_umap()
# - generate_dotplot()
source(file.path(module_dir, "scripts", "utils", "celltype-utils.R"))
Prepare input data
Read SCE object to get consensus cell types and UMAP coordinates:
merged_sce <- readRDS(sce_file)
# immediately remove assays we don't need for space
assay(merged_sce, "spliced") <- NULL
assay(merged_sce, "counts") <- NULL
reducedDim(merged_sce, "PCA") <- NULL
# Define libraries to remove
# See: https://github.com/AlexsLemonade/OpenScPCA-analysis/issues/1234#issuecomment-3113966395
remove_libraries <- c("SCPCL000124", "SCPCL001058")
keep_cells <- grep(
paste(remove_libraries, collapse="|"),
colnames(merged_sce),
invert = TRUE,
value = TRUE
)
merged_sce <- merged_sce[, keep_cells]
rm(keep_cells)
Read cell type data frames:
singler_df <- singler_files |>
# remove libraries
purrr::discard_at(remove_libraries) |>
purrr::map(readr::read_tsv) |>
purrr::list_rbind(names_to = "library_id") |>
dplyr::select(-delta.next, -labels) |>
dplyr::mutate(
# add cell id column for unique rows & joining
cell_id = glue::glue("{library_id}-{barcodes}"),
# recode NA -> "unknown_singler"
pruned.labels = ifelse(
is.na(pruned.labels),
"unknown_singler",
pruned.labels
)
)
# validation data frames
validation_df <- readr::read_tsv(validation_url) |>
dplyr::select(consensus_annotation, validation_group_annotation)
consensus_markers_df <- readr::read_tsv(consensus_markers_file)
nbatlas_markers_df <- readr::read_tsv(nbatlas_markers_file)
# set up palettes
# recoded to shared colors
recoded_palette_df <- readr::read_tsv(recoded_palette_file)
recoded_celltype_pal <- recoded_palette_df$hex_color
names(recoded_celltype_pal) <- recoded_palette_df$harmonized_label
# only the NBAtlas labels - use for validation dot plot
nbatlas_palette_df <- readr::read_tsv(nbatlas_palette_file)
nbatlas_celltype_pal <- nbatlas_palette_df$hex_color
names(nbatlas_celltype_pal) <- nbatlas_palette_df$NBAtlas_label
Join and prepare data for use:
celltype_df <- scuttle::makePerCellDF(
merged_sce,
use.coldata = c("barcodes", "sample_id", "library_id", "consensus_celltype_annotation"),
use.dimred = c("UMAP")
) |>
# there are repeated barcodes so we need to keep cell_id around
tibble::rownames_to_column("cell_id") |>
dplyr::rename(
UMAP1 = UMAP.1,
UMAP2 = UMAP.2,
consensus_annotation = consensus_celltype_annotation
) |>
dplyr::left_join(validation_df, by = "consensus_annotation") |>
# recode NAs to "unknown_consensus" and remove full consensus labels
dplyr::mutate(validation_group_annotation = ifelse(
is.na(validation_group_annotation),
"unknown_consensus",
validation_group_annotation
)) |>
dplyr::select(-consensus_annotation) |>
dplyr::left_join(singler_df, by = c("cell_id", "barcodes", "library_id")) |>
# we do specifically need to change pruned.labels from NE -> Neuroendocrine
dplyr::mutate(
pruned.labels = ifelse(pruned.labels == "NE", "Neuroendocrine", pruned.labels)
)
# Recode NBAtlas cell types where possible so they match with colors
celltype_recoded_df <- celltype_df |>
# rename to make annotation sources more clear
dplyr::rename(
"consensus_validation" = validation_group_annotation,
"singler" = pruned.labels
) |>
# pivot longer for wrangling
tidyr::pivot_longer(
c(consensus_validation, singler),
names_to = "annotation_type",
values_to = "label"
) |>
harmonize_celltypes(label, label_recoded)
Cell types in NBAtlas
Throughout this notebook, we compare the cell type labels from
SingleR with the consensus cell type labels. To facilitate
this, the resulting SingleR labels from
NBAtlas were grouped together to both a) mirror (where
possible) the broad consensus labels, and b) to make some visualizations
more manageable.
The table below shows the NBAtlas cell types and how
they are represented in visualizations, unless otherwise stated. Cells
labeled singler_unknown are those for which
SingleR could not reliably assign an annotation.
label_map_df <- celltype_recoded_df |>
dplyr::filter(annotation_type == "singler", label != "unknown_singler") |>
dplyr::select(label, label_recoded) |>
dplyr::distinct()
label_map_df |>
dplyr::group_by(label_recoded) |>
dplyr::summarize(all_labels = paste(label, collapse = ", ")) |>
dplyr::arrange(label_recoded) |>
dplyr::rename(
"Label shown in figures" = label_recoded,
"Underlying NBAltas label(s)" = all_labels
)
Heatmap
This section compares SingleR annotations to consensus
cell type annotations using a heatmap. The heatmap is colored by Jaccard
similarity index.
# Pivot wider for heatmap functions
celltype_recoded_wide_df <- celltype_recoded_df |>
dplyr::select(-label) |>
tidyr::pivot_wider(
names_from = annotation_type,
values_from = label_recoded
)
make_jaccard_heatmap(
celltype_recoded_wide_df,
"consensus_validation",
"singler",
"Consensus validation label",
"SingleR NBAtlas label"
)

We broadly see high compatibility between SingleR and
consensus labels. In addition, SingleR mostly classifies the unknown
consensus cells as Neuroendocrine, which is consistent with our
expectation that these are mostly tumor cells.
UMAPs
This section visualizes and compares SingleR annotations
to consensus cell type annotations using UMAPs. Note that the displayed
UMAP is from a merged object that has not been
batch-corrected.
Cell type labels have been harmonized between sources wherever
possible. Note that each set of labels has its own “stromal” category
which the labels distinguish. In addition, cells labeled
unknown_consensus are those with no assigned consensus
label, and cells labeled unknown_singler are those where
SingleR could not confidently assign a label.
Complete UMAP
First, we display the consensus and SingleR annotations
for all cells.
ggplot(celltype_recoded_df) +
aes(x = UMAP1, y = UMAP2, color = label_recoded) +
geom_point(size = 0.25, alpha = 0.5) +
scale_color_manual(
values = recoded_celltype_pal,
name = "Harmonized cell types"
) +
facet_wrap(vars(annotation_type)) +
coord_equal() +
theme(
legend.position = "bottom",
axis.ticks = element_blank(),
axis.text = element_blank()
) +
guides(color = guide_legend(override.aes = list(size = 2, alpha = 1)))

SingleR annotations only
Below we display a faceted UMAP to highlight the SingleR
annotations. Light gray cells in each panel represent all other cell
types.
celltype_recoded_df |>
dplyr::filter(annotation_type == "singler") |>
faceted_umap(
annotation_column = label_recoded,
celltype_colors = recoded_celltype_pal
)

Faceted comparison to consensus cell types
Below, we display faceted UMAPs highlighting a single cell type but
considering only cell types that have direct correspondence between
SingleR and consensus cell types. This allows us to see if
the normal cells that SingleR is labeling correspond well
to the normal cells identified by consensus labels. Each row displays a
cell type where the left column shows the consensus version and the
right column shows the SingleR version.
In addition, we include a category below putative-tumor
to directly compare the unknown consensus labels with the
Neuroendocrine cells labeled by SingleR. While
these categories are not necessarily directly comparable, they are each
most likely to contain tumor cells.
Also, note that the myeloid-labeled SingleR
cells are only neutrophils, but the consensus myeloid grouping
contains additional cell types.
direct_celltype_matches <- c(
"B cell",
"T cell",
"myeloid",
"macrophage",
"monocyte",
"dendritic cell",
"natural killer cell",
"fibroblast",
"endothelial cell",
"plasma cell",
"natural killer cell",
# we'll use this label to be able to directly compare unknown_consensus to Neuroendocrine
"putative-tumor"
)
celltype_facet_df <- celltype_recoded_df |>
dplyr::mutate(
# recode so Neuroendocrine matches with unknown_consensus in the plot
label_recoded = ifelse(
label_recoded %in% c("Neuroendocrine", "unknown_consensus"),
"putative-tumor",
label_recoded
)) |>
dplyr::filter(label_recoded %in% direct_celltype_matches)
faceted_umap(
celltype_facet_df,
annotation_column = label_recoded,
celltype_colors = recoded_celltype_pal,
facet_type = "grid",
annotation_type_column = annotation_type
)

There appears to be a reasonable correspondence between consensus-
and SingleR-colored UMAPS for each cell type. We see that
SingleR classified more cells compared to consensus, which
we expected, and we also see that the additional cells that
SingleR labeled are roughly in the same neighborhood as
their corresponding consensus labels.
Marker gene expression dot plots
In this section we’ll validate annotations using two sets of marker
genes:
- Marker genes identified in NBAtlas corresponding to the atlas cell
types
- This will tell us if we are picking up comparable signal in our data
that plays well with NBAtlas
- Consensus cell type marker genes
- This will provide an independent assessment of the reliability of
SingleR normal cell type assignments
# Prepare the expressed_genes vector
# we only care about if that gene is expressed otherwise we won't waste memory and include it
gene_sums <- rowData(merged_sce) |>
as.data.frame() |>
dplyr::select(contains("detected")) |>
as.matrix() |>
rowSums()
expressed_genes <- names(gene_sums)[gene_sums > 0]
NBAtlas marker genes
This dot plot shows the top-5 highest log2FC marker
genes per NBAtlas cell type for validation. Marker genes are taken
directly from the NBAtlas paper where they were identified with
Seurat::FindMarkers().
This plot does not show the recoded SingleR labels but
rather all labels that have associated marker genes; therefore, there
are more cell type categories in this plot than in other plots in this
report.
# data frame of top 5 upregulated genes per cell type
top_nbatlas_markers_df <- subset_nbatlas_markers(nbatlas_markers_df, 5, "up")
# we want to plot the literal labels here
full_celltype_df <- celltype_df |>
dplyr::filter(pruned.labels != "unknown_singler") |>
dplyr::select(cell_id, label = pruned.labels) |>
# collapse certain labels that are grouped in the marker genes
dplyr::mutate(
label = dplyr::case_when(
stringr::str_detect(label, "cDC") ~ "cDC",
stringr::str_detect(label, "monocyte") ~ "Monocyte",
.default = label
))
# Also update label_map_df to collapse those labels
label_map_df <- label_map_df |>
dplyr::mutate(
label = dplyr::case_when(
stringr::str_detect(label, "cDC") ~ "cDC",
stringr::str_detect(label, "monocyte") ~ "Monocyte",
.default = label)
) |>
unique()
# Prepare an order of label_recoded to help set the order for the literal underlying labels
label_coded_factor <- celltype_recoded_df |>
dplyr::filter(annotation_type == "singler", label_recoded != "unknown_singler") |>
dplyr::count(label_recoded, name = "total_cells") |>
dplyr::arrange(desc(total_cells)) |>
dplyr::pull(label_recoded)
label_recoded_factor <- factor(label_coded_factor, levels = label_coded_factor)
# get total number of cells per final annotation group and set up y_label
total_cells_df <- full_celltype_df |>
dplyr::count(label, name = "total_cells") |>
# order so cell types are grouped
dplyr::inner_join(label_map_df, by = "label" ) |>
dplyr::mutate(
label_recoded = as.factor(label_recoded),
label_recoded = forcats::fct_relevel(label_recoded, levels(label_recoded_factor))
) |>
dplyr::arrange(label_recoded) |>
dplyr::mutate(y_label = glue::glue("{label} ({total_cells})")) |>
# rename as expected for dotplot function - label_recoded needs to be the main column
dplyr::select(-label_recoded) |>
dplyr::rename(label_recoded = label)
total_cells_df$y_label <- factor(total_cells_df$y_label, levels = rev(total_cells_df$y_label))
nbatlas_bar_order <- total_cells_df$label_recoded
generate_dotplot(
merged_sce = merged_sce,
markers_df = top_nbatlas_markers_df,
# rename this after we've done all the wrangling
singler_df = full_celltype_df |> dplyr::rename(label_recoded = label),
total_cells_df = total_cells_df,
expressed_genes = expressed_genes,
bar_order = nbatlas_bar_order,
celltype_palette = nbatlas_celltype_pal,
min_cells = 0
)

For validation, we expect to see high marker gene expression along
the diagonal of the dot plot where cell type labels match with their
marker genes. We do broadly see high expression along the diagonal,
which means our labeled cells have some amount of similar signal to
those in NBAtlas. Some sets of marker genes show high expression across
multiple cell type categories in particular for closely related cell
types (e.g. monocyte, macrophage, myeloid, dendritic, or T-cells and
NK-cells), but there do not appear to be any strongly unexpected gene
expression patterns.
Importantly, these marker genes are not necessarily canonical marker
genes for the normal/immune cell types present, but were
empirically-determined based on the NBAtlas expression
patterns. There may therefore be overlapping marker genes between
categories, and the given marker genes may not be the “most” precise for
the cell types if analyzed independently.
Consensus validation marker genes
This dot plot shows expression of consensus validation marker genes
across matching cell types labeled with SingleR. Harmonized
SingleR labels with direct matches are shown on the top of
the y-axis (endothelial cell through
plasma cell), and the remaining SingleR labels
are below.
# Prepare data frame with singler labels to plot
singler_recoded_df <- celltype_recoded_wide_df |>
# we don't consider the NAs here
dplyr::filter(singler != "unknown_singler") |>
dplyr::select(cell_id, label_recoded = singler)
# to match up consensus and NBAtlas for ordering
direct_celltype_matches <- c(
"B cell",
"T cell",
"myeloid",
"macrophage",
"monocyte",
"fibroblast",
"dendritic cell",
"endothelial cell",
"plasma cell",
"natural killer cell"
)
total_cells_df <- singler_recoded_df |>
dplyr::count(label_recoded, name = "total_cells") |>
# labels that appear in both consensus+nbatlas should appear first
dplyr::mutate(in_both = label_recoded %in% direct_celltype_matches) |>
dplyr::group_by(in_both) |>
dplyr::arrange(total_cells, .by_group = TRUE) |>
dplyr::ungroup() |>
dplyr::mutate(y_label = glue::glue("{label_recoded} ({total_cells})"))
total_cells_df$y_label <- factor(total_cells_df$y_label, levels = total_cells_df$y_label)
# get the annotation bar order
consensus_bar_order <- total_cells_df |>
dplyr::filter(in_both) |>
dplyr::pull(label_recoded) |>
rev()
# prepare markers for dotplot
dotplot_consensus_markers_df <- consensus_markers_df |>
# consider only matching labels
dplyr::filter(validation_group_annotation %in% singler_recoded_df$label_recoded) |>
dplyr::select(
marker_gene_label = validation_group_annotation,
ensembl_gene_id,
gene_symbol
)
generate_dotplot(
merged_sce = merged_sce,
markers_df = dotplot_consensus_markers_df,
singler_df = singler_recoded_df,
total_cells_df = total_cells_df,
expressed_genes = expressed_genes,
bar_order = consensus_bar_order,
celltype_palette = recoded_celltype_pal,
min_cells = 0
)

Again, marker gene expression tends to be high in the matching cell
type categories, with the possible exception of B cell
which has somewhat lower gene expression of fewer genes; but, there is
still some expected expression there. We also see some “promiscuity”
where marker genes for closely-related cell types show high
expression.
Gene expression of unclassified consensus cells
Next, we look at expression of consensus validation marker genes for
each normal/harmonized cell type label. We’re specifically interested in
the marker gene expression for cells that were classified by
SingleR but have an unknown consensus cell type. We assume
the unknown consensus cells may be more likely to be tumor because they
could not be robustly classified otherwise, but since some of those
cells were called “normal” by SingleR we’d like to get a
sense of whether they really are normal.
We have two groups:
SingleR cells classified as
Neuroendocrine; these are putatively tumor cells
- We expect that these will have higher
Neuroendocrine
marker gene expression and lower normal gene expression
SingleR cells not classified as
Neuroendocrine; these are more likely to be normal cells
- We expect that these will have higher normal marker gene
expression
For this, we’ll consider the average gene expression among the top 20
NBAtlas marker genes for each cell type.
# data frame of top 20 upregulated genes per cell type
top_nbatlas_markers_df <- subset_nbatlas_markers(nbatlas_markers_df, 20, "up")
unknown_consensus_ids <- celltype_recoded_wide_df |>
dplyr::filter(consensus_validation == "unknown_consensus") |>
dplyr::pull(cell_id)
unknown_consensus_celltype_df <- full_celltype_df |>
dplyr::filter(cell_id %in% unknown_consensus_ids) |>
dplyr::mutate(
singler = ifelse(
label == "Neuroendocrine", "NE (likely tumor)", "Non-NE (likely normal)")
) |>
dplyr::select(-label)
goi <- unique(top_nbatlas_markers_df$ensembl_gene_id)
compare_annotations_expr_df <- scuttle::makePerCellDF(
merged_sce[goi, unknown_consensus_ids],
features = goi,
assay.type = "logcounts",
use.coldata = "cell_id",
use.dimred = FALSE
) |>
tidyr::pivot_longer(starts_with("ENSG"), names_to = "ensembl_gene_id", values_to = "logcounts") |>
# filter to only cells with expression
dplyr::filter(logcounts > 0) |>
# join the annotations and marker gene data frame; use inner since we've filtered already
dplyr::inner_join(unknown_consensus_celltype_df, by = "cell_id") |>
dplyr::left_join(top_nbatlas_markers_df, by = "ensembl_gene_id", relationship = "many-to-many") |>
# average by marker_gene_label
dplyr::group_by(singler, marker_gene_label, ensembl_gene_id) |>
dplyr::summarize(mean_exp = mean(logcounts)) |>
dplyr::ungroup() |>
dplyr::mutate(marker_gene_label = factor(marker_gene_label),
marker_gene_label = forcats::fct_relevel(marker_gene_label, nbatlas_bar_order))
ggplot(compare_annotations_expr_df) +
aes(x = marker_gene_label, y = mean_exp, color = singler) +
ggforce::geom_sina(position = position_dodge(width = 0.7)) +
labs(
x = "NBAtlas marker gene label",
y = "Mean expression across marker genes",
color = "SingleR annotation"
) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1)
)

The left-most gene set on the x-axis is Neuroendocrine,
and normal cell types follow. This plot suggests that the normal cells
SingleR is picking up are likely to be normal cells: normal
gene expression for non-NE cells is higher compared to NE cells, and we
see the opposite trend for the Neuroendocrine marker
genes.
Inferences from smaller vs. larger libraries
In this section, we briefly look at annotation results for libraries
with fewer than 1000 cells which may be lower quality than other
libraries.
Those libraries are:
libraries_low_cells <- celltype_recoded_df |>
dplyr::filter(annotation_type == "singler") |>
dplyr::select(-annotation_type) |>
dplyr::count(library_id, name = "total_cells") |>
dplyr::filter(total_cells < 1000)
libraries_low_cells
Distirbution of cell types
celltype_recoded_wide_df |>
dplyr::mutate(
library_size = ifelse(library_id %in% libraries_low_cells$library_id, "<1000 cells", ">1000 cells")
) |>
ggplot() +
aes(x = library_id, fill = singler) +
geom_bar(position = "fill") +
scale_fill_manual(values = recoded_celltype_pal) +
facet_wrap(vars(library_size), scales = "free_x") +
scale_y_continuous(expand = c(0,0)) +
theme(
legend.position = "bottom",
axis.text.x = element_blank()
)

Although there are only four libraries with <1000 cells so it is
challenging to make definitive conclusions, it does not seem like the
overall cell type distributions are very different between smaller and
larger libraries.
Heatmap
This section shows a heatmap comparing annotations from these
libraries only to the consensus annotations.
low_cells_df <- celltype_recoded_wide_df |>
dplyr::filter(library_id %in% libraries_low_cells$library_id)
make_jaccard_heatmap(
low_cells_df,
"consensus_validation",
"singler",
"Consensus validation label",
"SingleR NBAtlas label"
)

The heatmap comparison to consensus cell types for these libraries
looks reasonable; corresponding cell types tend to match up. Note that
because this heatmap includes fewer libraries, there are fewer cell
types to display overall.
Percent of unclassified cells
Next, we’ll ask whether the percent of unclassified cells (aka,
unknown_singler) can be explained by library size. Below,
we plot the relationship between total cells and fraction of cells that
SingleR failed to confidently classify.
unknown_singler_df <- celltype_recoded_wide_df |>
dplyr::group_by(library_id) |>
dplyr::summarize(unknown_cells= sum(singler == "unknown_singler"))
percent_unclassified_df <- celltype_recoded_wide_df |>
dplyr::add_count(library_id, name = "total_cells") |>
dplyr::inner_join(unknown_singler_df, by = "library_id") |>
dplyr::select(library_id, total_cells, unknown_cells) |>
dplyr::distinct() |>
dplyr::mutate(frac_singler_unclassified = unknown_cells/total_cells)
ggplot(percent_unclassified_df) +
aes(x = total_cells, y = frac_singler_unclassified) +
geom_point() +
geom_smooth(method = "lm")
`geom_smooth()` using formula = 'y ~ x'

There doesn’t appear to be a relationship here, and further the
percent of unclassified cells are all under 7% with most below around
4%.
Inferences from PDX libraries
In this section, we briefly look at annotation results for libraries
from PDX samples, which may show distinct trends. Here is the breakdown
of the number of PDX vs tissue libraries:
# This was being developed while this bug was present, so we have to check the type first
# https://github.com/AlexsLemonade/scpcaTools/issues/302
pdx_class <- class(colData(merged_sce)$is_xenograft)
if (pdx_class == "character") {
pdx_val <- "TRUE"
} else {
pdx_val <- TRUE
}
pdx_libraries <- colData(merged_sce) |>
as.data.frame() |>
dplyr::filter(is_xenograft == pdx_val) |>
dplyr::pull(library_id) |>
unique()
celltype_recoded_wide_df <- celltype_recoded_wide_df |>
dplyr::mutate(sample_type = ifelse(library_id %in% pdx_libraries, "pdx", "tissue"))
celltype_recoded_wide_df |>
dplyr::select(library_id, sample_type) |>
dplyr::distinct() |>
dplyr::count(sample_type)
Distribution of cell types
We’ll first look at the cell type distributions for each library
between sample types:
ggplot(celltype_recoded_wide_df) +
aes(x = library_id, fill = singler) +
geom_bar(position = "fill") +
scale_fill_manual(values = recoded_celltype_pal) +
facet_wrap(vars(sample_type), scales = "free_x") +
scale_y_continuous(expand = c(0,0)) +
theme(
legend.position = "bottom",
axis.text.x = element_blank()
)

PDX samples have, not unexpectedly, much less cell type diversity
overall with most cells being tumor or similar and immune cells rarely
present. This is consistent with expectations in general for the biology
of PDX’s.
Heatmap
This section shows a heatmap compaing SingleR annotations to
consensus cell types, for PDX samples only.
make_jaccard_heatmap(
celltype_recoded_wide_df |> dplyr::filter(sample_type == "pdx"),
"consensus_validation",
"singler",
"Consensus validation label",
"SingleR NBAtlas label"
)

This heatmap comparison seems reasonable, again taking into
consideration that there are relatively fewer cell types in the PDX
samples, and corresponding cell types tend to match up.
Session Info
# record the versions of the packages used in this analysis and other environment information
sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.5
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/New_York
tzcode source: internal
attached base packages:
[1] stats4 stats graphics grDevices datasets utils methods
[8] base
other attached packages:
[1] SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0
[3] Biobase_2.64.0 GenomicRanges_1.56.2
[5] GenomeInfoDb_1.40.1 IRanges_2.38.1
[7] S4Vectors_0.42.1 BiocGenerics_0.50.0
[9] MatrixGenerics_1.16.0 matrixStats_1.5.0
[11] patchwork_1.3.0 ggplot2_3.5.2
loaded via a namespace (and not attached):
[1] bitops_1.0-9 rlang_1.1.6
[3] magrittr_2.0.3 clue_0.3-66
[5] GetoptLong_1.0.5 compiler_4.4.0
[7] mgcv_1.9-1 DelayedMatrixStats_1.26.0
[9] png_0.1-8 vctrs_0.6.5
[11] stringr_1.5.1 pkgconfig_2.0.3
[13] shape_1.4.6.1 crayon_1.5.3
[15] fastmap_1.2.0 XVector_0.44.0
[17] scuttle_1.14.0 labeling_0.4.3
[19] rmarkdown_2.29 tzdb_0.5.0
[21] UCSC.utils_1.0.0 purrr_1.0.4
[23] bit_4.6.0 xfun_0.52
[25] zlibbioc_1.50.0 cachem_1.1.0
[27] beachmat_2.20.0 jsonlite_2.0.0
[29] DelayedArray_0.30.1 BiocParallel_1.38.0
[31] tweenr_2.0.3 jpeg_0.1-11
[33] parallel_4.4.0 cluster_2.1.8
[35] R6_2.6.1 bslib_0.9.0
[37] stringi_1.8.7 RColorBrewer_1.1-3
[39] jquerylib_0.1.4 Rcpp_1.0.14
[41] iterators_1.0.14 knitr_1.50
[43] readr_2.1.5 splines_4.4.0
[45] Matrix_1.7-1 tidyselect_1.2.1
[47] abind_1.4-8 yaml_2.3.10
[49] doParallel_1.0.17 codetools_0.2-20
[51] curl_6.3.0 lattice_0.22-6
[53] tibble_3.3.0 plyr_1.8.9
[55] withr_3.0.2 evaluate_1.0.3
[57] polyclip_1.10-7 circlize_0.4.16
[59] pillar_1.10.2 BiocManager_1.30.26
[61] renv_1.1.3 foreach_1.5.2
[63] generics_0.1.4 vroom_1.6.5
[65] rprojroot_2.0.4 hms_1.1.3
[67] sparseMatrixStats_1.16.0 scales_1.4.0
[69] glue_1.8.0 tools_4.4.0
[71] forcats_1.0.0 grid_4.4.0
[73] Cairo_1.6-2 tidyr_1.3.1
[75] colorspace_2.1-1 nlme_3.1-166
[77] GenomeInfoDbData_1.2.12 ggforce_0.5.0
[79] cli_3.6.5 ggmap_4.0.1
[81] S4Arrays_1.4.1 viridisLite_0.4.2
[83] ComplexHeatmap_2.20.0 dplyr_1.1.4
[85] gtable_0.3.6 sass_0.4.10
[87] digest_0.6.37 SparseArray_1.4.8
[89] rjson_0.2.23 farver_2.1.2
[91] htmltools_0.5.8.1 lifecycle_1.0.4
[93] httr_1.4.7 GlobalOptions_0.1.2
[95] bit64_4.6.0-1 MASS_7.3-64
LS0tCnRpdGxlOiAiRXhwbG9yZSB0aGUgU2luZ2xlUiByZXN1bHRzIgphdXRob3I6ICJTdGVwaGFuaWUgSi4gU3BpZWxtYW4iCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKVGhpcyBub3RlYm9va3MgZXhwbG9yZXMgdGhlIHJlc3VsdHMgZnJvbSBydW5uaW5nIGNlbGwgdHlwZSBhbm5vdGF0aW9uIHdpdGggYFNpbmdsZVJgIHVzaW5nIHRoZSBOQkF0bGFzLgpUaGUgTkJBdGxhcyByZWZlcmVuY2Ugd2FzIGFnZ3JlZ2F0ZWQgd2l0aCBgU2luZ2xlUmAgcHJpb3IgdG8gbW9kZWwgdHJhaW5pbmcuCgpJbiB0aGlzIG5vdGVib29rLCB3ZSB2aXN1YWxpemUgaW5mZXJyZWQgY2VsbCB0eXBlIGFubm90YXRpb25zIGRpcmVjdGx5LCBjb21wYXJlIHRoZW0gdG8gbm9ybWFsIGNvbnNlbnN1cyBjZWxsIHR5cGVzLCBhbmQgdmFsaWRhdGUgY2VsbCB0eXBlIGFzc2lnbm1lbnRzIHdpdGggbWFya2VyIGdlbmVzLgoKCiMjIFNldHVwCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQpvcHRpb25zKHJlYWRyLnNob3dfY29sX3R5cGVzID0gRkFMU0UpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCn0pCgp0aGVtZV9zZXQodGhlbWVfYncoKSkKCiMgRGVmaW5lIGNvbG9yIHJhbXAgZm9yIHNoYXJlZCB1c2UgaW4gdGhlIGhlYXRtYXBzCmhlYXRtYXBfY29sX2Z1biA8LSBjaXJjbGl6ZTo6Y29sb3JSYW1wMihjKDAsIDEpLCBjb2xvcnMgPSBjKCJ3aGl0ZSIsICJkYXJrc2xhdGVibHVlIikpCiMgU2V0IGhlYXRtYXAgcGFkZGluZyBvcHRpb24KQ29tcGxleEhlYXRtYXA6Omh0X29wdChUSVRMRV9QQURESU5HID0gZ3JpZDo6dW5pdCgwLjYsICJpbiIpKQoKIyBzZXR0aW5ncwpvcHRpb25zKAogIGRwbHlyLnN1bW1hcmlzZS5pbmZvcm0gPSBGQUxTRSwgCiAgcmVhZHIuc2hvd19jb2xfdHlwZXMgPSBGQUxTRQopCmBgYAoKCiMjIyBQYXRocwoKYGBge3IgYmFzZSBwYXRoc30KIyBUaGUgYmFzZSBwYXRoIGZvciB0aGUgT3BlblNjUENBIHJlcG9zaXRvcnksIGZvdW5kIGJ5IGl0cyAoaGlkZGVuKSAuZ2l0IGRpcmVjdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKCm1vZHVsZV9kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImNlbGwtdHlwZS1uZXVyb2JsYXN0b21hLTA0IikKcmVmX2RpciA8LSBmaWxlLnBhdGgobW9kdWxlX2RpciwgInJlZmVyZW5jZXMiKQpyZXN1bHRzX2RpciA8LSBmaWxlLnBhdGgobW9kdWxlX2RpciwgInJlc3VsdHMiLCAic2luZ2xlciIpCmRhdGFfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJkYXRhIiwgImN1cnJlbnQiLCAiU0NQQ1AwMDAwMDQiKQptZXJnZWRfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJkYXRhIiwgImN1cnJlbnQiLCAicmVzdWx0cyIsICJtZXJnZS1zY2UiLCAiU0NQQ1AwMDAwMDQiKQpgYGAKCgpgYGB7ciBmaWxlIHBhdGhzfQojIFNpbmdsZVIgZmlsZXMKc2luZ2xlcl9maWxlcyA8LSBsaXN0LmZpbGVzKAogIHBhdGggPSByZXN1bHRzX2RpciwKICBwYXR0ZXJuID0gIl9zaW5nbGVyLWFubm90YXRpb25zXFwudHN2IiwKICByZWN1cnNpdmUgPSBUUlVFLAogIGZ1bGwubmFtZXMgPSBUUlVFCikgfD4KICAjIGFkZCBuYW1lcyBhcyBsaWJyYXJ5IGlkCiAgcHVycnI6OnNldF9uYW1lcygKICAgIFwoeCkgewogICAgICBzdHJpbmdyOjpzdHJfcmVtb3ZlKGJhc2VuYW1lKHgpLCAiX3NpbmdsZXItYW5ub3RhdGlvbnMudHN2IikKICAgIH0KICApCgojIG1lcmdlZCBTQ0UgZmlsZQpzY2VfZmlsZSA8LSBmaWxlLnBhdGgoCiAgbWVyZ2VkX2RpciwKICAiU0NQQ1AwMDAwMDRfbWVyZ2VkLnJkcyIKKQoKIyBicm9hZCBjb25zZW5zdXMgY2VsbCB0eXBlIGdyb3Vwcwp2YWxpZGF0aW9uX3VybCA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0FsZXhzTGVtb25hZGUvT3BlblNjUENBLWFuYWx5c2lzL3JlZnMvaGVhZHMvbWFpbi9hbmFseXNlcy9jZWxsLXR5cGUtY29uc2Vuc3VzL3JlZmVyZW5jZXMvY29uc2Vuc3VzLXZhbGlkYXRpb24tZ3JvdXBzLnRzdiIKCiMgcGFsZXR0ZSBmaWxlcwpyZWNvZGVkX3BhbGV0dGVfZmlsZSA8LSBmaWxlLnBhdGgoCiAgbW9kdWxlX2RpciwKICAicGFsZXR0ZXMiLAogICJoYXJtb25pemVkLWNlbGwtdHlwZS1wYWxldHRlLnRzdiIKKQoKbmJhdGxhc19wYWxldHRlX2ZpbGUgPC0gZmlsZS5wYXRoKAogIG1vZHVsZV9kaXIsCiAgInBhbGV0dGVzIiwKICAibmJhdGxhcy1tYXJrZXItZ2VuZXMtcGFsZXR0ZS50c3YiCikKCiMgbWFya2VyIGdlbmVzIGZvciB2YWxpZGF0aW9uCmNvbnNlbnN1c19tYXJrZXJzX2ZpbGUgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9BbGV4c0xlbW9uYWRlL09wZW5TY1BDQS1hbmFseXNpcy9yZWZzL2hlYWRzL21haW4vYW5hbHlzZXMvY2VsbC10eXBlLWNvbnNlbnN1cy9yZWZlcmVuY2VzL3ZhbGlkYXRpb24tbWFya2Vycy50c3YiCm5iYXRsYXNfbWFya2Vyc19maWxlIDwtIGZpbGUucGF0aChyZWZfZGlyLCAibmJhdGxhcy1tYXJrZXItZ2VuZXMudHN2IikKYGBgCgojIyMgRnVuY3Rpb25zCgpgYGB7cn0KIyBTb3VyY2UgSmFjY2FyZCBhbmQgaGVhdG1hcCB1dGlsaXRpZXMgZnVuY3Rpb25zCnNvdXJjZShmaWxlLnBhdGgobW9kdWxlX2RpciwgInNjcmlwdHMiLCAidXRpbHMiLCAiamFjY2FyZC11dGlscy5SIikpCgojIFNvdXJjZSBhZGRpdGlvbmFsIHV0aWxpdGllcyBmdW5jdGlvbnM6CiMgLSBzZWxlY3RfbmJhdGxhc19tYXJrZXJzKCkKIyAtIGhhcm1vbml6ZV9jZWxsdHlwZXMoKQojIC0gZmFjZXRlZF91bWFwKCkKIyAtIGdlbmVyYXRlX2RvdHBsb3QoKQpzb3VyY2UoZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJzY3JpcHRzIiwgInV0aWxzIiwgImNlbGx0eXBlLXV0aWxzLlIiKSkKYGBgCgoKIyMjIFByZXBhcmUgaW5wdXQgZGF0YQoKUmVhZCBTQ0Ugb2JqZWN0IHRvIGdldCBjb25zZW5zdXMgY2VsbCB0eXBlcyBhbmQgVU1BUCBjb29yZGluYXRlczoKCmBgYHtyfQptZXJnZWRfc2NlIDwtIHJlYWRSRFMoc2NlX2ZpbGUpCiMgaW1tZWRpYXRlbHkgcmVtb3ZlIGFzc2F5cyB3ZSBkb24ndCBuZWVkIGZvciBzcGFjZQphc3NheShtZXJnZWRfc2NlLCAic3BsaWNlZCIpIDwtIE5VTEwKYXNzYXkobWVyZ2VkX3NjZSwgImNvdW50cyIpIDwtIE5VTEwKcmVkdWNlZERpbShtZXJnZWRfc2NlLCAiUENBIikgPC0gTlVMTAoKIyBEZWZpbmUgbGlicmFyaWVzIHRvIHJlbW92ZQojIFNlZTogaHR0cHM6Ly9naXRodWIuY29tL0FsZXhzTGVtb25hZGUvT3BlblNjUENBLWFuYWx5c2lzL2lzc3Vlcy8xMjM0I2lzc3VlY29tbWVudC0zMTEzOTY2Mzk1CnJlbW92ZV9saWJyYXJpZXMgPC0gYygiU0NQQ0wwMDAxMjQiLCAiU0NQQ0wwMDEwNTgiKQprZWVwX2NlbGxzIDwtIGdyZXAoCiAgcGFzdGUocmVtb3ZlX2xpYnJhcmllcywgY29sbGFwc2U9InwiKSwgCiAgY29sbmFtZXMobWVyZ2VkX3NjZSksIAogIGludmVydCA9IFRSVUUsCiAgdmFsdWUgPSBUUlVFCikKCm1lcmdlZF9zY2UgPC0gbWVyZ2VkX3NjZVssIGtlZXBfY2VsbHNdCnJtKGtlZXBfY2VsbHMpCmBgYAoKUmVhZCBjZWxsIHR5cGUgZGF0YSBmcmFtZXM6CgpgYGB7cn0Kc2luZ2xlcl9kZiA8LSBzaW5nbGVyX2ZpbGVzIHw+CiAgIyByZW1vdmUgbGlicmFyaWVzCiAgcHVycnI6OmRpc2NhcmRfYXQocmVtb3ZlX2xpYnJhcmllcykgfD4KICBwdXJycjo6bWFwKHJlYWRyOjpyZWFkX3RzdikgfD4KICBwdXJycjo6bGlzdF9yYmluZChuYW1lc190byA9ICJsaWJyYXJ5X2lkIikgfD4KICBkcGx5cjo6c2VsZWN0KC1kZWx0YS5uZXh0LCAtbGFiZWxzKSB8PgogIGRwbHlyOjptdXRhdGUoCiAgICAjIGFkZCBjZWxsIGlkIGNvbHVtbiBmb3IgdW5pcXVlIHJvd3MgJiBqb2luaW5nCiAgICBjZWxsX2lkID0gZ2x1ZTo6Z2x1ZSgie2xpYnJhcnlfaWR9LXtiYXJjb2Rlc30iKSwKICAgICMgcmVjb2RlIE5BIC0+ICJ1bmtub3duX3NpbmdsZXIiCiAgICBwcnVuZWQubGFiZWxzID0gaWZlbHNlKAogICAgICBpcy5uYShwcnVuZWQubGFiZWxzKSwKICAgICAgInVua25vd25fc2luZ2xlciIsCiAgICAgIHBydW5lZC5sYWJlbHMKICAgICkKICApCgojIHZhbGlkYXRpb24gZGF0YSBmcmFtZXMKdmFsaWRhdGlvbl9kZiA8LSByZWFkcjo6cmVhZF90c3YodmFsaWRhdGlvbl91cmwpIHw+CiAgZHBseXI6OnNlbGVjdChjb25zZW5zdXNfYW5ub3RhdGlvbiwgdmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uKQpjb25zZW5zdXNfbWFya2Vyc19kZiA8LSByZWFkcjo6cmVhZF90c3YoY29uc2Vuc3VzX21hcmtlcnNfZmlsZSkKbmJhdGxhc19tYXJrZXJzX2RmIDwtIHJlYWRyOjpyZWFkX3RzdihuYmF0bGFzX21hcmtlcnNfZmlsZSkKCgojIHNldCB1cCBwYWxldHRlcwoKIyByZWNvZGVkIHRvIHNoYXJlZCBjb2xvcnMKcmVjb2RlZF9wYWxldHRlX2RmIDwtIHJlYWRyOjpyZWFkX3RzdihyZWNvZGVkX3BhbGV0dGVfZmlsZSkKcmVjb2RlZF9jZWxsdHlwZV9wYWwgPC0gcmVjb2RlZF9wYWxldHRlX2RmJGhleF9jb2xvcgpuYW1lcyhyZWNvZGVkX2NlbGx0eXBlX3BhbCkgPC0gcmVjb2RlZF9wYWxldHRlX2RmJGhhcm1vbml6ZWRfbGFiZWwKCiMgb25seSB0aGUgTkJBdGxhcyBsYWJlbHMgLSB1c2UgZm9yIHZhbGlkYXRpb24gZG90IHBsb3QKbmJhdGxhc19wYWxldHRlX2RmIDwtIHJlYWRyOjpyZWFkX3RzdihuYmF0bGFzX3BhbGV0dGVfZmlsZSkKbmJhdGxhc19jZWxsdHlwZV9wYWwgPC0gbmJhdGxhc19wYWxldHRlX2RmJGhleF9jb2xvcgpuYW1lcyhuYmF0bGFzX2NlbGx0eXBlX3BhbCkgPC0gbmJhdGxhc19wYWxldHRlX2RmJE5CQXRsYXNfbGFiZWwKCmBgYAoKSm9pbiBhbmQgcHJlcGFyZSBkYXRhIGZvciB1c2U6CgpgYGB7cn0KY2VsbHR5cGVfZGYgPC0gc2N1dHRsZTo6bWFrZVBlckNlbGxERigKICBtZXJnZWRfc2NlLAogIHVzZS5jb2xkYXRhID0gYygiYmFyY29kZXMiLCAic2FtcGxlX2lkIiwgImxpYnJhcnlfaWQiLCAiY29uc2Vuc3VzX2NlbGx0eXBlX2Fubm90YXRpb24iKSwKICB1c2UuZGltcmVkID0gYygiVU1BUCIpCikgfD4KICAjIHRoZXJlIGFyZSByZXBlYXRlZCBiYXJjb2RlcyBzbyB3ZSBuZWVkIHRvIGtlZXAgY2VsbF9pZCBhcm91bmQKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiY2VsbF9pZCIpIHw+CiAgZHBseXI6OnJlbmFtZSgKICAgIFVNQVAxID0gVU1BUC4xLAogICAgVU1BUDIgPSBVTUFQLjIsCiAgICBjb25zZW5zdXNfYW5ub3RhdGlvbiA9IGNvbnNlbnN1c19jZWxsdHlwZV9hbm5vdGF0aW9uCiAgKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4odmFsaWRhdGlvbl9kZiwgYnkgPSAiY29uc2Vuc3VzX2Fubm90YXRpb24iKSB8PgogICMgcmVjb2RlIE5BcyB0byAidW5rbm93bl9jb25zZW5zdXMiIGFuZCByZW1vdmUgZnVsbCBjb25zZW5zdXMgbGFiZWxzCiAgZHBseXI6Om11dGF0ZSh2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24gPSBpZmVsc2UoCiAgICBpcy5uYSh2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24pLAogICAgInVua25vd25fY29uc2Vuc3VzIiwKICAgIHZhbGlkYXRpb25fZ3JvdXBfYW5ub3RhdGlvbgogICkpIHw+CiAgZHBseXI6OnNlbGVjdCgtY29uc2Vuc3VzX2Fubm90YXRpb24pIHw+CiAgZHBseXI6OmxlZnRfam9pbihzaW5nbGVyX2RmLCBieSA9IGMoImNlbGxfaWQiLCAiYmFyY29kZXMiLCAibGlicmFyeV9pZCIpKSB8PgogICMgd2UgZG8gc3BlY2lmaWNhbGx5IG5lZWQgdG8gY2hhbmdlIHBydW5lZC5sYWJlbHMgZnJvbSBORSAtPiBOZXVyb2VuZG9jcmluZQogIGRwbHlyOjptdXRhdGUoCiAgICBwcnVuZWQubGFiZWxzID0gaWZlbHNlKHBydW5lZC5sYWJlbHMgPT0gIk5FIiwgIk5ldXJvZW5kb2NyaW5lIiwgcHJ1bmVkLmxhYmVscykKICApCgojIFJlY29kZSBOQkF0bGFzIGNlbGwgdHlwZXMgd2hlcmUgcG9zc2libGUgc28gdGhleSBtYXRjaCB3aXRoIGNvbG9ycwpjZWxsdHlwZV9yZWNvZGVkX2RmIDwtIGNlbGx0eXBlX2RmIHw+CiAgIyByZW5hbWUgdG8gbWFrZSBhbm5vdGF0aW9uIHNvdXJjZXMgbW9yZSBjbGVhcgogIGRwbHlyOjpyZW5hbWUoCiAgICAiY29uc2Vuc3VzX3ZhbGlkYXRpb24iID0gdmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uLAogICAgInNpbmdsZXIiID0gcHJ1bmVkLmxhYmVscwogICkgfD4KICAjIHBpdm90IGxvbmdlciBmb3Igd3JhbmdsaW5nCiAgdGlkeXI6OnBpdm90X2xvbmdlcigKICAgIGMoY29uc2Vuc3VzX3ZhbGlkYXRpb24sIHNpbmdsZXIpLAogICAgbmFtZXNfdG8gPSAiYW5ub3RhdGlvbl90eXBlIiwKICAgIHZhbHVlc190byA9ICJsYWJlbCIKICApIHw+IAogIGhhcm1vbml6ZV9jZWxsdHlwZXMobGFiZWwsIGxhYmVsX3JlY29kZWQpCiAgCmBgYAoKCiMjIENlbGwgdHlwZXMgaW4gTkJBdGxhcwoKVGhyb3VnaG91dCB0aGlzIG5vdGVib29rLCB3ZSBjb21wYXJlIHRoZSBjZWxsIHR5cGUgbGFiZWxzIGZyb20gYFNpbmdsZVJgIHdpdGggdGhlIGNvbnNlbnN1cyBjZWxsIHR5cGUgbGFiZWxzLgpUbyBmYWNpbGl0YXRlIHRoaXMsIHRoZSByZXN1bHRpbmcgYFNpbmdsZVJgIGxhYmVscyBmcm9tIGBOQkF0bGFzYCB3ZXJlIGdyb3VwZWQgdG9nZXRoZXIgdG8gYm90aCBhKSBtaXJyb3IgKHdoZXJlIHBvc3NpYmxlKSB0aGUgYnJvYWQgY29uc2Vuc3VzIGxhYmVscywgYW5kIGIpIHRvIG1ha2Ugc29tZSB2aXN1YWxpemF0aW9ucyBtb3JlIG1hbmFnZWFibGUuCgpUaGUgdGFibGUgYmVsb3cgc2hvd3MgdGhlIGBOQkF0bGFzYCBjZWxsIHR5cGVzIGFuZCBob3cgdGhleSBhcmUgcmVwcmVzZW50ZWQgaW4gdmlzdWFsaXphdGlvbnMsIHVubGVzcyBvdGhlcndpc2Ugc3RhdGVkLgpDZWxscyBsYWJlbGVkIGBzaW5nbGVyX3Vua25vd25gIGFyZSB0aG9zZSBmb3Igd2hpY2ggYFNpbmdsZVJgIGNvdWxkIG5vdCByZWxpYWJseSBhc3NpZ24gYW4gYW5ub3RhdGlvbi4KCgpgYGB7cn0KbGFiZWxfbWFwX2RmIDwtIGNlbGx0eXBlX3JlY29kZWRfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGFubm90YXRpb25fdHlwZSA9PSAic2luZ2xlciIsIGxhYmVsICE9ICJ1bmtub3duX3NpbmdsZXIiKSB8PgogIGRwbHlyOjpzZWxlY3QobGFiZWwsIGxhYmVsX3JlY29kZWQpIHw+CiAgZHBseXI6OmRpc3RpbmN0KCkKCmxhYmVsX21hcF9kZiB8PgogIGRwbHlyOjpncm91cF9ieShsYWJlbF9yZWNvZGVkKSB8PgogIGRwbHlyOjpzdW1tYXJpemUoYWxsX2xhYmVscyA9IHBhc3RlKGxhYmVsLCBjb2xsYXBzZSA9ICIsICIpKSB8PgogIGRwbHlyOjphcnJhbmdlKGxhYmVsX3JlY29kZWQpIHw+CiAgZHBseXI6OnJlbmFtZSgKICAgICJMYWJlbCBzaG93biBpbiBmaWd1cmVzIiA9IGxhYmVsX3JlY29kZWQsCiAgICAiVW5kZXJseWluZyBOQkFsdGFzIGxhYmVsKHMpIiA9IGFsbF9sYWJlbHMKICApCmBgYAoKCgojIyBIZWF0bWFwCgpUaGlzIHNlY3Rpb24gY29tcGFyZXMgYFNpbmdsZVJgIGFubm90YXRpb25zIHRvIGNvbnNlbnN1cyBjZWxsIHR5cGUgYW5ub3RhdGlvbnMgdXNpbmcgYSBoZWF0bWFwLgpUaGUgaGVhdG1hcCBpcyBjb2xvcmVkIGJ5IEphY2NhcmQgc2ltaWxhcml0eSBpbmRleC4KCmBgYHtyLCBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gOH0gCiMgUGl2b3Qgd2lkZXIgZm9yIGhlYXRtYXAgZnVuY3Rpb25zCmNlbGx0eXBlX3JlY29kZWRfd2lkZV9kZiA8LSBjZWxsdHlwZV9yZWNvZGVkX2RmIHw+CiAgZHBseXI6OnNlbGVjdCgtbGFiZWwpIHw+CiAgdGlkeXI6OnBpdm90X3dpZGVyKAogICAgbmFtZXNfZnJvbSA9IGFubm90YXRpb25fdHlwZSwKICAgIHZhbHVlc19mcm9tID0gbGFiZWxfcmVjb2RlZAogICkKCm1ha2VfamFjY2FyZF9oZWF0bWFwKAogIGNlbGx0eXBlX3JlY29kZWRfd2lkZV9kZiwKICAiY29uc2Vuc3VzX3ZhbGlkYXRpb24iLAogICJzaW5nbGVyIiwKICAiQ29uc2Vuc3VzIHZhbGlkYXRpb24gbGFiZWwiLAogICJTaW5nbGVSIE5CQXRsYXMgbGFiZWwiCikKYGBgCgpXZSBicm9hZGx5IHNlZSBoaWdoIGNvbXBhdGliaWxpdHkgYmV0d2VlbiBgU2luZ2xlUmAgYW5kIGNvbnNlbnN1cyBsYWJlbHMuCkluIGFkZGl0aW9uLCBTaW5nbGVSIG1vc3RseSBjbGFzc2lmaWVzIHRoZSB1bmtub3duIGNvbnNlbnN1cyBjZWxscyBhcyBOZXVyb2VuZG9jcmluZSwgd2hpY2ggaXMgY29uc2lzdGVudCB3aXRoIG91ciBleHBlY3RhdGlvbiB0aGF0IHRoZXNlIGFyZSBtb3N0bHkgdHVtb3IgY2VsbHMuCgojIyBVTUFQcwoKVGhpcyBzZWN0aW9uIHZpc3VhbGl6ZXMgYW5kIGNvbXBhcmVzIGBTaW5nbGVSYCBhbm5vdGF0aW9ucyB0byBjb25zZW5zdXMgY2VsbCB0eXBlIGFubm90YXRpb25zIHVzaW5nIFVNQVBzLgpOb3RlIHRoYXQgdGhlIGRpc3BsYXllZCBVTUFQIGlzIGZyb20gYSBtZXJnZWQgb2JqZWN0IHRoYXQgaGFzIF9ub3QgYmVlbiBiYXRjaC1jb3JyZWN0ZWQuXwoKQ2VsbCB0eXBlIGxhYmVscyBoYXZlIGJlZW4gaGFybW9uaXplZCBiZXR3ZWVuIHNvdXJjZXMgd2hlcmV2ZXIgcG9zc2libGUuCk5vdGUgdGhhdCBlYWNoIHNldCBvZiBsYWJlbHMgaGFzIGl0cyBvd24gInN0cm9tYWwiIGNhdGVnb3J5IHdoaWNoIHRoZSBsYWJlbHMgZGlzdGluZ3Vpc2guCkluIGFkZGl0aW9uLCBjZWxscyBsYWJlbGVkIGB1bmtub3duX2NvbnNlbnN1c2AgYXJlIHRob3NlIHdpdGggbm8gYXNzaWduZWQgY29uc2Vuc3VzIGxhYmVsLCBhbmQgY2VsbHMgbGFiZWxlZCBgdW5rbm93bl9zaW5nbGVyYCBhcmUgdGhvc2Ugd2hlcmUgYFNpbmdsZVJgIGNvdWxkIG5vdCBjb25maWRlbnRseSBhc3NpZ24gYSBsYWJlbC4KCiMjIyBDb21wbGV0ZSBVTUFQCgpGaXJzdCwgd2UgZGlzcGxheSB0aGUgY29uc2Vuc3VzIGFuZCBgU2luZ2xlUmAgYW5ub3RhdGlvbnMgZm9yIGFsbCBjZWxscy4KCmBgYHtyLCBmaWcud2lkdGggPSAxNCwgZmlnLmhlaWdodCA9IDd9CmdncGxvdChjZWxsdHlwZV9yZWNvZGVkX2RmKSArCiAgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGxhYmVsX3JlY29kZWQpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjI1LCBhbHBoYSA9IDAuNSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgIHZhbHVlcyA9IHJlY29kZWRfY2VsbHR5cGVfcGFsLAogICAgbmFtZSA9ICJIYXJtb25pemVkIGNlbGwgdHlwZXMiCiAgKSArCiAgZmFjZXRfd3JhcCh2YXJzKGFubm90YXRpb25fdHlwZSkpICsKICBjb29yZF9lcXVhbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKQogICkgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyLCBhbHBoYSA9IDEpKSkKYGBgCgojIyMgU2luZ2xlUiBhbm5vdGF0aW9ucyBvbmx5CgpCZWxvdyB3ZSBkaXNwbGF5IGEgZmFjZXRlZCBVTUFQIHRvIGhpZ2hsaWdodCB0aGUgYFNpbmdsZVJgIGFubm90YXRpb25zLgpMaWdodCBncmF5IGNlbGxzIGluIGVhY2ggcGFuZWwgcmVwcmVzZW50IGFsbCBvdGhlciBjZWxsIHR5cGVzLgoKYGBge3IgZmlnLndpZHRoID0gMTYsIGZpZy5oZWlnaHQgPSAxNn0KY2VsbHR5cGVfcmVjb2RlZF9kZiB8PgogIGRwbHlyOjpmaWx0ZXIoYW5ub3RhdGlvbl90eXBlID09ICJzaW5nbGVyIikgfD4KICBmYWNldGVkX3VtYXAoCiAgICBhbm5vdGF0aW9uX2NvbHVtbiA9IGxhYmVsX3JlY29kZWQsCiAgICBjZWxsdHlwZV9jb2xvcnMgPSByZWNvZGVkX2NlbGx0eXBlX3BhbAogICkKYGBgCgoKIyMjIEZhY2V0ZWQgY29tcGFyaXNvbiB0byBjb25zZW5zdXMgY2VsbCB0eXBlcwoKQmVsb3csIHdlIGRpc3BsYXkgZmFjZXRlZCBVTUFQcyBoaWdobGlnaHRpbmcgYSBzaW5nbGUgY2VsbCB0eXBlIGJ1dCBjb25zaWRlcmluZyBvbmx5IGNlbGwgdHlwZXMgdGhhdCBoYXZlIGRpcmVjdCBjb3JyZXNwb25kZW5jZSBiZXR3ZWVuIGBTaW5nbGVSYCBhbmQgY29uc2Vuc3VzIGNlbGwgdHlwZXMuClRoaXMgYWxsb3dzIHVzIHRvIHNlZSBpZiB0aGUgbm9ybWFsIGNlbGxzIHRoYXQgYFNpbmdsZVJgIGlzIGxhYmVsaW5nIGNvcnJlc3BvbmQgd2VsbCB0byB0aGUgbm9ybWFsIGNlbGxzIGlkZW50aWZpZWQgYnkgY29uc2Vuc3VzIGxhYmVscy4KRWFjaCByb3cgZGlzcGxheXMgYSBjZWxsIHR5cGUgd2hlcmUgdGhlIGxlZnQgY29sdW1uIHNob3dzIHRoZSBjb25zZW5zdXMgdmVyc2lvbiBhbmQgdGhlIHJpZ2h0IGNvbHVtbiBzaG93cyB0aGUgYFNpbmdsZVJgIHZlcnNpb24uCgoKSW4gYWRkaXRpb24sIHdlIGluY2x1ZGUgYSBjYXRlZ29yeSBiZWxvdyBgcHV0YXRpdmUtdHVtb3JgIHRvIGRpcmVjdGx5IGNvbXBhcmUgdGhlIHVua25vd24gY29uc2Vuc3VzIGxhYmVscyB3aXRoIHRoZSBgTmV1cm9lbmRvY3JpbmVgIGNlbGxzIGxhYmVsZWQgYnkgYFNpbmdsZVIuYCAKV2hpbGUgdGhlc2UgY2F0ZWdvcmllcyBhcmUgbm90IG5lY2Vzc2FyaWx5IGRpcmVjdGx5IGNvbXBhcmFibGUsIHRoZXkgYXJlIGVhY2ggbW9zdCBsaWtlbHkgdG8gY29udGFpbiB0dW1vciBjZWxscy4KCkFsc28sIG5vdGUgdGhhdCB0aGUgYG15ZWxvaWRgLWxhYmVsZWQgYFNpbmdsZVJgIGNlbGxzIGFyZSBfb25seV8gbmV1dHJvcGhpbHMsIGJ1dCB0aGUgY29uc2Vuc3VzIG15ZWxvaWQgZ3JvdXBpbmcgY29udGFpbnMgYWRkaXRpb25hbCBjZWxsIHR5cGVzLgoKYGBge3IsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTI4fQpkaXJlY3RfY2VsbHR5cGVfbWF0Y2hlcyA8LSBjKAogICJCIGNlbGwiLAogICJUIGNlbGwiLAogICJteWVsb2lkIiwKICAibWFjcm9waGFnZSIsCiAgIm1vbm9jeXRlIiwKICAiZGVuZHJpdGljIGNlbGwiLAogICJuYXR1cmFsIGtpbGxlciBjZWxsIiwKICAiZmlicm9ibGFzdCIsCiAgImVuZG90aGVsaWFsIGNlbGwiLAogICJwbGFzbWEgY2VsbCIsCiAgIm5hdHVyYWwga2lsbGVyIGNlbGwiLAogICMgd2UnbGwgdXNlIHRoaXMgbGFiZWwgdG8gYmUgYWJsZSB0byBkaXJlY3RseSBjb21wYXJlIHVua25vd25fY29uc2Vuc3VzIHRvIE5ldXJvZW5kb2NyaW5lCiAgInB1dGF0aXZlLXR1bW9yIgopCgoKY2VsbHR5cGVfZmFjZXRfZGYgPC0gY2VsbHR5cGVfcmVjb2RlZF9kZiB8PgogIGRwbHlyOjptdXRhdGUoCiAgICAjIHJlY29kZSBzbyBOZXVyb2VuZG9jcmluZSBtYXRjaGVzIHdpdGggdW5rbm93bl9jb25zZW5zdXMgaW4gdGhlIHBsb3QKICAgIGxhYmVsX3JlY29kZWQgPSBpZmVsc2UoCiAgICAgIGxhYmVsX3JlY29kZWQgJWluJSBjKCJOZXVyb2VuZG9jcmluZSIsICJ1bmtub3duX2NvbnNlbnN1cyIpLAogICAgICAicHV0YXRpdmUtdHVtb3IiLAogICAgICBsYWJlbF9yZWNvZGVkCiAgICApKSB8PgogIGRwbHlyOjpmaWx0ZXIobGFiZWxfcmVjb2RlZCAlaW4lIGRpcmVjdF9jZWxsdHlwZV9tYXRjaGVzKQoKZmFjZXRlZF91bWFwKAogIGNlbGx0eXBlX2ZhY2V0X2RmLAogIGFubm90YXRpb25fY29sdW1uID0gbGFiZWxfcmVjb2RlZCwKICBjZWxsdHlwZV9jb2xvcnMgPSByZWNvZGVkX2NlbGx0eXBlX3BhbCwKICBmYWNldF90eXBlID0gImdyaWQiLAogIGFubm90YXRpb25fdHlwZV9jb2x1bW4gPSBhbm5vdGF0aW9uX3R5cGUKKQpgYGAKClRoZXJlIGFwcGVhcnMgdG8gYmUgYSByZWFzb25hYmxlIGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gY29uc2Vuc3VzLSBhbmQgYFNpbmdsZVJgLWNvbG9yZWQgVU1BUFMgZm9yIGVhY2ggY2VsbCB0eXBlLgpXZSBzZWUgdGhhdCBgU2luZ2xlUmAgY2xhc3NpZmllZCBtb3JlIGNlbGxzIGNvbXBhcmVkIHRvIGNvbnNlbnN1cywgd2hpY2ggd2UgZXhwZWN0ZWQsIGFuZCB3ZSBhbHNvIHNlZSB0aGF0IHRoZSBhZGRpdGlvbmFsIGNlbGxzIHRoYXQgYFNpbmdsZVJgIGxhYmVsZWQgYXJlIHJvdWdobHkgaW4gdGhlIHNhbWUgbmVpZ2hib3Job29kIGFzIHRoZWlyIGNvcnJlc3BvbmRpbmcgY29uc2Vuc3VzIGxhYmVscy4KCgojIyBNYXJrZXIgZ2VuZSBleHByZXNzaW9uIGRvdCBwbG90cwoKSW4gdGhpcyBzZWN0aW9uIHdlJ2xsIHZhbGlkYXRlIGFubm90YXRpb25zIHVzaW5nIHR3byBzZXRzIG9mIG1hcmtlciBnZW5lczoKCiogTWFya2VyIGdlbmVzIGlkZW50aWZpZWQgaW4gTkJBdGxhcyBjb3JyZXNwb25kaW5nIHRvIHRoZSBhdGxhcyBjZWxsIHR5cGVzCiAgKiBUaGlzIHdpbGwgdGVsbCB1cyBpZiB3ZSBhcmUgcGlja2luZyB1cCBjb21wYXJhYmxlIHNpZ25hbCBpbiBvdXIgZGF0YSB0aGF0IHBsYXlzIHdlbGwgd2l0aCBOQkF0bGFzCiogQ29uc2Vuc3VzIGNlbGwgdHlwZSBtYXJrZXIgZ2VuZXMKICAqIFRoaXMgd2lsbCBwcm92aWRlIGFuIGluZGVwZW5kZW50IGFzc2Vzc21lbnQgb2YgdGhlIHJlbGlhYmlsaXR5IG9mIGBTaW5nbGVSYCBub3JtYWwgY2VsbCB0eXBlIGFzc2lnbm1lbnRzIAoKYGBge3J9CiMgUHJlcGFyZSB0aGUgZXhwcmVzc2VkX2dlbmVzIHZlY3RvcgojIHdlIG9ubHkgY2FyZSBhYm91dCBpZiB0aGF0IGdlbmUgaXMgZXhwcmVzc2VkIG90aGVyd2lzZSB3ZSB3b24ndCB3YXN0ZSBtZW1vcnkgYW5kIGluY2x1ZGUgaXQKZ2VuZV9zdW1zIDwtIHJvd0RhdGEobWVyZ2VkX3NjZSkgfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICBkcGx5cjo6c2VsZWN0KGNvbnRhaW5zKCJkZXRlY3RlZCIpKSB8PgogIGFzLm1hdHJpeCgpIHw+CiAgcm93U3VtcygpCmV4cHJlc3NlZF9nZW5lcyA8LSBuYW1lcyhnZW5lX3N1bXMpW2dlbmVfc3VtcyA+IDBdCmBgYAoKCgojIyMgTkJBdGxhcyBtYXJrZXIgZ2VuZXMKClRoaXMgZG90IHBsb3Qgc2hvd3MgdGhlIHRvcC01IGhpZ2hlc3QgYGxvZzJGQ2AgbWFya2VyIGdlbmVzIHBlciBOQkF0bGFzIGNlbGwgdHlwZSBmb3IgdmFsaWRhdGlvbi4KTWFya2VyIGdlbmVzIGFyZSB0YWtlbiBkaXJlY3RseSBmcm9tIHRoZSBOQkF0bGFzIHBhcGVyIHdoZXJlIHRoZXkgd2VyZSBpZGVudGlmaWVkIHdpdGggYFNldXJhdDo6RmluZE1hcmtlcnMoKWAuIAoKVGhpcyBwbG90IGRvZXMgbm90IHNob3cgdGhlIHJlY29kZWQgYFNpbmdsZVJgIGxhYmVscyBidXQgcmF0aGVyIGFsbCBsYWJlbHMgdGhhdCBoYXZlIGFzc29jaWF0ZWQgbWFya2VyIGdlbmVzOyB0aGVyZWZvcmUsIHRoZXJlIGFyZSBtb3JlIGNlbGwgdHlwZSBjYXRlZ29yaWVzIGluIHRoaXMgcGxvdCB0aGFuIGluIG90aGVyIHBsb3RzIGluIHRoaXMgcmVwb3J0LgoKYGBge3J9CiMgZGF0YSBmcmFtZSBvZiB0b3AgNSB1cHJlZ3VsYXRlZCBnZW5lcyBwZXIgY2VsbCB0eXBlCnRvcF9uYmF0bGFzX21hcmtlcnNfZGYgPC0gc3Vic2V0X25iYXRsYXNfbWFya2VycyhuYmF0bGFzX21hcmtlcnNfZGYsIDUsICJ1cCIpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGggPSAyNCwgZmlnLmhlaWdodCA9IDEyfQojIHdlIHdhbnQgdG8gcGxvdCB0aGUgbGl0ZXJhbCBsYWJlbHMgaGVyZQpmdWxsX2NlbGx0eXBlX2RmIDwtIGNlbGx0eXBlX2RmIHw+CiAgZHBseXI6OmZpbHRlcihwcnVuZWQubGFiZWxzICE9ICJ1bmtub3duX3NpbmdsZXIiKSB8PgogIGRwbHlyOjpzZWxlY3QoY2VsbF9pZCwgbGFiZWwgPSBwcnVuZWQubGFiZWxzKSB8PgogICMgY29sbGFwc2UgY2VydGFpbiBsYWJlbHMgdGhhdCBhcmUgZ3JvdXBlZCBpbiB0aGUgbWFya2VyIGdlbmVzCiAgZHBseXI6Om11dGF0ZSgKICAgbGFiZWwgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgIHN0cmluZ3I6OnN0cl9kZXRlY3QobGFiZWwsICJjREMiKSB+ICJjREMiLAogICAgIHN0cmluZ3I6OnN0cl9kZXRlY3QobGFiZWwsICJtb25vY3l0ZSIpIH4gIk1vbm9jeXRlIiwKICAgICAuZGVmYXVsdCA9IGxhYmVsCiAgKSkKCgojIEFsc28gdXBkYXRlIGxhYmVsX21hcF9kZiB0byBjb2xsYXBzZSB0aG9zZSBsYWJlbHMKbGFiZWxfbWFwX2RmIDwtIGxhYmVsX21hcF9kZiB8PgogIGRwbHlyOjptdXRhdGUoCiAgIGxhYmVsID0gZHBseXI6OmNhc2Vfd2hlbigKICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KGxhYmVsLCAiY0RDIikgfiAiY0RDIiwKICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KGxhYmVsLCAibW9ub2N5dGUiKSB+ICJNb25vY3l0ZSIsCiAgICAgLmRlZmF1bHQgPSBsYWJlbCkKICApIHw+CiAgdW5pcXVlKCkKCiMgUHJlcGFyZSBhbiBvcmRlciBvZiBsYWJlbF9yZWNvZGVkIHRvIGhlbHAgc2V0IHRoZSBvcmRlciBmb3IgdGhlIGxpdGVyYWwgdW5kZXJseWluZyBsYWJlbHMKbGFiZWxfY29kZWRfZmFjdG9yIDwtIGNlbGx0eXBlX3JlY29kZWRfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGFubm90YXRpb25fdHlwZSA9PSAic2luZ2xlciIsIGxhYmVsX3JlY29kZWQgIT0gInVua25vd25fc2luZ2xlciIpIHw+CiAgZHBseXI6OmNvdW50KGxhYmVsX3JlY29kZWQsIG5hbWUgPSAidG90YWxfY2VsbHMiKSB8PgogIGRwbHlyOjphcnJhbmdlKGRlc2ModG90YWxfY2VsbHMpKSB8PgogIGRwbHlyOjpwdWxsKGxhYmVsX3JlY29kZWQpCmxhYmVsX3JlY29kZWRfZmFjdG9yIDwtIGZhY3RvcihsYWJlbF9jb2RlZF9mYWN0b3IsIGxldmVscyA9IGxhYmVsX2NvZGVkX2ZhY3RvcikKCiMgZ2V0IHRvdGFsIG51bWJlciBvZiBjZWxscyBwZXIgZmluYWwgYW5ub3RhdGlvbiBncm91cCBhbmQgc2V0IHVwIHlfbGFiZWwKdG90YWxfY2VsbHNfZGYgPC0gZnVsbF9jZWxsdHlwZV9kZiB8PgogIGRwbHlyOjpjb3VudChsYWJlbCwgbmFtZSA9ICJ0b3RhbF9jZWxscyIpIHw+CiAgIyBvcmRlciBzbyBjZWxsIHR5cGVzIGFyZSBncm91cGVkCiAgZHBseXI6OmlubmVyX2pvaW4obGFiZWxfbWFwX2RmLCBieSA9ICJsYWJlbCIgKSB8PgogIGRwbHlyOjptdXRhdGUoCiAgICBsYWJlbF9yZWNvZGVkID0gYXMuZmFjdG9yKGxhYmVsX3JlY29kZWQpLAogICAgbGFiZWxfcmVjb2RlZCA9IGZvcmNhdHM6OmZjdF9yZWxldmVsKGxhYmVsX3JlY29kZWQsIGxldmVscyhsYWJlbF9yZWNvZGVkX2ZhY3RvcikpCiAgKSB8PgogIGRwbHlyOjphcnJhbmdlKGxhYmVsX3JlY29kZWQpIHw+CiAgZHBseXI6Om11dGF0ZSh5X2xhYmVsID0gZ2x1ZTo6Z2x1ZSgie2xhYmVsfSAoe3RvdGFsX2NlbGxzfSkiKSkgfD4KICAjIHJlbmFtZSBhcyBleHBlY3RlZCBmb3IgZG90cGxvdCBmdW5jdGlvbiAtIGxhYmVsX3JlY29kZWQgbmVlZHMgdG8gYmUgdGhlIG1haW4gY29sdW1uCiAgZHBseXI6OnNlbGVjdCgtbGFiZWxfcmVjb2RlZCkgfD4KICBkcGx5cjo6cmVuYW1lKGxhYmVsX3JlY29kZWQgPSBsYWJlbCkKCgp0b3RhbF9jZWxsc19kZiR5X2xhYmVsIDwtIGZhY3Rvcih0b3RhbF9jZWxsc19kZiR5X2xhYmVsLCBsZXZlbHMgPSByZXYodG90YWxfY2VsbHNfZGYkeV9sYWJlbCkpCm5iYXRsYXNfYmFyX29yZGVyIDwtIHRvdGFsX2NlbGxzX2RmJGxhYmVsX3JlY29kZWQKYGBgCgoKYGBge3IsIGZpZy53aWR0aCA9IDMyLCBmaWcuaGVpZ2h0ID0gMTZ9CmdlbmVyYXRlX2RvdHBsb3QoCiAgbWVyZ2VkX3NjZSA9IG1lcmdlZF9zY2UsCiAgbWFya2Vyc19kZiA9IHRvcF9uYmF0bGFzX21hcmtlcnNfZGYsCiAgIyByZW5hbWUgdGhpcyBhZnRlciB3ZSd2ZSBkb25lIGFsbCB0aGUgd3JhbmdsaW5nCiAgc2luZ2xlcl9kZiA9IGZ1bGxfY2VsbHR5cGVfZGYgfD4gZHBseXI6OnJlbmFtZShsYWJlbF9yZWNvZGVkID0gbGFiZWwpLAogIHRvdGFsX2NlbGxzX2RmID0gdG90YWxfY2VsbHNfZGYsCiAgZXhwcmVzc2VkX2dlbmVzID0gZXhwcmVzc2VkX2dlbmVzLAogIGJhcl9vcmRlciA9IG5iYXRsYXNfYmFyX29yZGVyLCAKICBjZWxsdHlwZV9wYWxldHRlID0gbmJhdGxhc19jZWxsdHlwZV9wYWwsIAogIG1pbl9jZWxscyA9IDAKKQpgYGAKRm9yIHZhbGlkYXRpb24sIHdlIGV4cGVjdCB0byBzZWUgaGlnaCBtYXJrZXIgZ2VuZSBleHByZXNzaW9uIGFsb25nIHRoZSBkaWFnb25hbCBvZiB0aGUgZG90IHBsb3Qgd2hlcmUgY2VsbCB0eXBlIGxhYmVscyBtYXRjaCB3aXRoIHRoZWlyIG1hcmtlciBnZW5lcy4KV2UgZG8gYnJvYWRseSBzZWUgaGlnaCBleHByZXNzaW9uIGFsb25nIHRoZSBkaWFnb25hbCwgd2hpY2ggbWVhbnMgb3VyIGxhYmVsZWQgY2VsbHMgaGF2ZSBzb21lIGFtb3VudCBvZiBzaW1pbGFyIHNpZ25hbCB0byB0aG9zZSBpbiBOQkF0bGFzLgpTb21lIHNldHMgb2YgbWFya2VyIGdlbmVzIHNob3cgaGlnaCBleHByZXNzaW9uIGFjcm9zcyBtdWx0aXBsZSBjZWxsIHR5cGUgY2F0ZWdvcmllcyBpbiBwYXJ0aWN1bGFyIGZvciBjbG9zZWx5IHJlbGF0ZWQgY2VsbCB0eXBlcyAoZS5nLiBtb25vY3l0ZSwgbWFjcm9waGFnZSwgbXllbG9pZCwgZGVuZHJpdGljLCBvciBULWNlbGxzIGFuZCBOSy1jZWxscyksIGJ1dCB0aGVyZSBkbyBub3QgYXBwZWFyIHRvIGJlIGFueSBzdHJvbmdseSB1bmV4cGVjdGVkIGdlbmUgZXhwcmVzc2lvbiBwYXR0ZXJucy4KCkltcG9ydGFudGx5LCB0aGVzZSBtYXJrZXIgZ2VuZXMgYXJlIG5vdCBuZWNlc3NhcmlseSBjYW5vbmljYWwgbWFya2VyIGdlbmVzIGZvciB0aGUgbm9ybWFsL2ltbXVuZSBjZWxsIHR5cGVzIHByZXNlbnQsIGJ1dCB3ZXJlIGVtcGlyaWNhbGx5LWRldGVybWluZWQgYmFzZWQgb24gdGhlIGBOQkF0bGFzYCBleHByZXNzaW9uIHBhdHRlcm5zLgpUaGVyZSBtYXkgdGhlcmVmb3JlIGJlIG92ZXJsYXBwaW5nIG1hcmtlciBnZW5lcyBiZXR3ZWVuIGNhdGVnb3JpZXMsIGFuZCB0aGUgZ2l2ZW4gbWFya2VyIGdlbmVzIG1heSBub3QgYmUgdGhlICJtb3N0IiBwcmVjaXNlIGZvciB0aGUgY2VsbCB0eXBlcyBpZiBhbmFseXplZCBpbmRlcGVuZGVudGx5LgoKIyMjIENvbnNlbnN1cyB2YWxpZGF0aW9uIG1hcmtlciBnZW5lcwoKVGhpcyBkb3QgcGxvdCBzaG93cyBleHByZXNzaW9uIG9mIGNvbnNlbnN1cyB2YWxpZGF0aW9uIG1hcmtlciBnZW5lcyBhY3Jvc3MgbWF0Y2hpbmcgY2VsbCB0eXBlcyBsYWJlbGVkIHdpdGggYFNpbmdsZVJgLgpIYXJtb25pemVkIGBTaW5nbGVSYCBsYWJlbHMgd2l0aCBkaXJlY3QgbWF0Y2hlcyBhcmUgc2hvd24gb24gdGhlIHRvcCBvZiB0aGUgeS1heGlzIChgZW5kb3RoZWxpYWwgY2VsbGAgdGhyb3VnaCBgcGxhc21hIGNlbGxgKSwgYW5kIHRoZSByZW1haW5pbmcgYFNpbmdsZVJgIGxhYmVscyBhcmUgYmVsb3cuCgpgYGB7cn0KIyBQcmVwYXJlIGRhdGEgZnJhbWUgd2l0aCBzaW5nbGVyIGxhYmVscyB0byBwbG90CnNpbmdsZXJfcmVjb2RlZF9kZiA8LSBjZWxsdHlwZV9yZWNvZGVkX3dpZGVfZGYgfD4KICAjIHdlIGRvbid0IGNvbnNpZGVyIHRoZSBOQXMgaGVyZQogIGRwbHlyOjpmaWx0ZXIoc2luZ2xlciAhPSAidW5rbm93bl9zaW5nbGVyIikgfD4KICBkcGx5cjo6c2VsZWN0KGNlbGxfaWQsIGxhYmVsX3JlY29kZWQgPSBzaW5nbGVyKQoKIyB0byBtYXRjaCB1cCBjb25zZW5zdXMgYW5kIE5CQXRsYXMgZm9yIG9yZGVyaW5nCmRpcmVjdF9jZWxsdHlwZV9tYXRjaGVzIDwtIGMoCiAgIkIgY2VsbCIsCiAgIlQgY2VsbCIsCiAgIm15ZWxvaWQiLAogICJtYWNyb3BoYWdlIiwgCiAgIm1vbm9jeXRlIiwKICAiZmlicm9ibGFzdCIsCiAgImRlbmRyaXRpYyBjZWxsIiwKICAiZW5kb3RoZWxpYWwgY2VsbCIsCiAgInBsYXNtYSBjZWxsIiwKICAibmF0dXJhbCBraWxsZXIgY2VsbCIKKQoKdG90YWxfY2VsbHNfZGYgPC0gc2luZ2xlcl9yZWNvZGVkX2RmIHw+CiAgZHBseXI6OmNvdW50KGxhYmVsX3JlY29kZWQsIG5hbWUgPSAidG90YWxfY2VsbHMiKSB8PgogICMgbGFiZWxzIHRoYXQgYXBwZWFyIGluIGJvdGggY29uc2Vuc3VzK25iYXRsYXMgc2hvdWxkIGFwcGVhciBmaXJzdAogIGRwbHlyOjptdXRhdGUoaW5fYm90aCA9IGxhYmVsX3JlY29kZWQgJWluJSBkaXJlY3RfY2VsbHR5cGVfbWF0Y2hlcykgfD4KICBkcGx5cjo6Z3JvdXBfYnkoaW5fYm90aCkgfD4KICBkcGx5cjo6YXJyYW5nZSh0b3RhbF9jZWxscywgLmJ5X2dyb3VwID0gVFJVRSkgfD4KICBkcGx5cjo6dW5ncm91cCgpIHw+CiAgZHBseXI6Om11dGF0ZSh5X2xhYmVsID0gZ2x1ZTo6Z2x1ZSgie2xhYmVsX3JlY29kZWR9ICh7dG90YWxfY2VsbHN9KSIpKQp0b3RhbF9jZWxsc19kZiR5X2xhYmVsIDwtIGZhY3Rvcih0b3RhbF9jZWxsc19kZiR5X2xhYmVsLCBsZXZlbHMgPSB0b3RhbF9jZWxsc19kZiR5X2xhYmVsKQoKIyBnZXQgdGhlIGFubm90YXRpb24gYmFyIG9yZGVyCmNvbnNlbnN1c19iYXJfb3JkZXIgPC0gdG90YWxfY2VsbHNfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGluX2JvdGgpIHw+CiAgZHBseXI6OnB1bGwobGFiZWxfcmVjb2RlZCkgfD4KICByZXYoKQpgYGAKCgoKYGBge3J9CiMgcHJlcGFyZSBtYXJrZXJzIGZvciBkb3RwbG90CmRvdHBsb3RfY29uc2Vuc3VzX21hcmtlcnNfZGYgPC0gY29uc2Vuc3VzX21hcmtlcnNfZGYgfD4KICAjIGNvbnNpZGVyIG9ubHkgbWF0Y2hpbmcgbGFiZWxzCiAgZHBseXI6OmZpbHRlcih2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24gJWluJSBzaW5nbGVyX3JlY29kZWRfZGYkbGFiZWxfcmVjb2RlZCkgfD4KICBkcGx5cjo6c2VsZWN0KAogICAgbWFya2VyX2dlbmVfbGFiZWwgPSB2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24sCiAgICBlbnNlbWJsX2dlbmVfaWQsCiAgICBnZW5lX3N5bWJvbAogICkKYGBgCgoKYGBge3IsIGZpZy53aWR0aCA9IDI2LCBmaWcuaGVpZ2h0ID0gMTJ9CmdlbmVyYXRlX2RvdHBsb3QoCiAgbWVyZ2VkX3NjZSA9IG1lcmdlZF9zY2UsCiAgbWFya2Vyc19kZiA9IGRvdHBsb3RfY29uc2Vuc3VzX21hcmtlcnNfZGYsCiAgc2luZ2xlcl9kZiA9IHNpbmdsZXJfcmVjb2RlZF9kZiwKICB0b3RhbF9jZWxsc19kZiA9IHRvdGFsX2NlbGxzX2RmLAogIGV4cHJlc3NlZF9nZW5lcyA9IGV4cHJlc3NlZF9nZW5lcywKICBiYXJfb3JkZXIgPSBjb25zZW5zdXNfYmFyX29yZGVyLCAKICBjZWxsdHlwZV9wYWxldHRlID0gcmVjb2RlZF9jZWxsdHlwZV9wYWwsIAogIG1pbl9jZWxscyA9IDAKKQpgYGAKCkFnYWluLCBtYXJrZXIgZ2VuZSBleHByZXNzaW9uIHRlbmRzIHRvIGJlIGhpZ2ggaW4gdGhlIG1hdGNoaW5nIGNlbGwgdHlwZSBjYXRlZ29yaWVzLCB3aXRoIHRoZSBwb3NzaWJsZSBleGNlcHRpb24gb2YgYEIgY2VsbGAgd2hpY2ggaGFzIHNvbWV3aGF0IGxvd2VyIGdlbmUgZXhwcmVzc2lvbiBvZiBmZXdlciBnZW5lczsgYnV0LCB0aGVyZSBpcyBzdGlsbCBzb21lIGV4cGVjdGVkIGV4cHJlc3Npb24gdGhlcmUuCldlIGFsc28gc2VlIHNvbWUgInByb21pc2N1aXR5IiB3aGVyZSBtYXJrZXIgZ2VuZXMgZm9yIGNsb3NlbHktcmVsYXRlZCBjZWxsIHR5cGVzIHNob3cgaGlnaCBleHByZXNzaW9uLiAKCgojIyBHZW5lIGV4cHJlc3Npb24gb2YgdW5jbGFzc2lmaWVkIGNvbnNlbnN1cyBjZWxscyAKCk5leHQsIHdlIGxvb2sgYXQgZXhwcmVzc2lvbiBvZiBjb25zZW5zdXMgdmFsaWRhdGlvbiBtYXJrZXIgZ2VuZXMgZm9yIGVhY2ggbm9ybWFsL2hhcm1vbml6ZWQgY2VsbCB0eXBlIGxhYmVsLgpXZSdyZSBzcGVjaWZpY2FsbHkgaW50ZXJlc3RlZCBpbiB0aGUgbWFya2VyIGdlbmUgZXhwcmVzc2lvbiBmb3IgY2VsbHMgdGhhdCB3ZXJlIGNsYXNzaWZpZWQgYnkgYFNpbmdsZVJgIGJ1dCBoYXZlIGFuIHVua25vd24gY29uc2Vuc3VzIGNlbGwgdHlwZS4KV2UgYXNzdW1lIHRoZSB1bmtub3duIGNvbnNlbnN1cyBjZWxscyBtYXkgYmUgbW9yZSBsaWtlbHkgdG8gYmUgdHVtb3IgYmVjYXVzZSB0aGV5IGNvdWxkIG5vdCBiZSByb2J1c3RseSBjbGFzc2lmaWVkIG90aGVyd2lzZSwgYnV0IHNpbmNlIHNvbWUgb2YgdGhvc2UgY2VsbHMgd2VyZSBjYWxsZWQgIm5vcm1hbCIgYnkgYFNpbmdsZVJgIHdlJ2QgbGlrZSB0byBnZXQgYSBzZW5zZSBvZiB3aGV0aGVyIHRoZXkgcmVhbGx5IGFyZSBub3JtYWwuCgpXZSBoYXZlIHR3byBncm91cHM6CgoqIGBTaW5nbGVSYCBjZWxscyBjbGFzc2lmaWVkIGFzIGBOZXVyb2VuZG9jcmluZWA7IHRoZXNlIGFyZSBwdXRhdGl2ZWx5IHR1bW9yIGNlbGxzCiAgKiBXZSBleHBlY3QgdGhhdCB0aGVzZSB3aWxsIGhhdmUgaGlnaGVyIGBOZXVyb2VuZG9jcmluZWAgbWFya2VyIGdlbmUgZXhwcmVzc2lvbiBhbmQgbG93ZXIgbm9ybWFsIGdlbmUgZXhwcmVzc2lvbgoqIGBTaW5nbGVSYCBjZWxscyBub3QgY2xhc3NpZmllZCBhcyBgTmV1cm9lbmRvY3JpbmVgOyB0aGVzZSBhcmUgbW9yZSBsaWtlbHkgdG8gYmUgbm9ybWFsIGNlbGxzCiAgKiBXZSBleHBlY3QgdGhhdCB0aGVzZSB3aWxsIGhhdmUgaGlnaGVyIG5vcm1hbCBtYXJrZXIgZ2VuZSBleHByZXNzaW9uCgpGb3IgdGhpcywgd2UnbGwgY29uc2lkZXIgdGhlIGF2ZXJhZ2UgZ2VuZSBleHByZXNzaW9uIGFtb25nIHRoZSB0b3AgMjAgTkJBdGxhcyBtYXJrZXIgZ2VuZXMgZm9yIGVhY2ggY2VsbCB0eXBlLgoKYGBge3J9CiMgZGF0YSBmcmFtZSBvZiB0b3AgMjAgdXByZWd1bGF0ZWQgZ2VuZXMgcGVyIGNlbGwgdHlwZQp0b3BfbmJhdGxhc19tYXJrZXJzX2RmIDwtIHN1YnNldF9uYmF0bGFzX21hcmtlcnMobmJhdGxhc19tYXJrZXJzX2RmLCAyMCwgInVwIikKCnVua25vd25fY29uc2Vuc3VzX2lkcyA8LSBjZWxsdHlwZV9yZWNvZGVkX3dpZGVfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGNvbnNlbnN1c192YWxpZGF0aW9uID09ICJ1bmtub3duX2NvbnNlbnN1cyIpIHw+CiAgZHBseXI6OnB1bGwoY2VsbF9pZCkgCgp1bmtub3duX2NvbnNlbnN1c19jZWxsdHlwZV9kZiA8LSBmdWxsX2NlbGx0eXBlX2RmIHw+CiAgZHBseXI6OmZpbHRlcihjZWxsX2lkICVpbiUgdW5rbm93bl9jb25zZW5zdXNfaWRzKSB8PgogIGRwbHlyOjptdXRhdGUoCiAgICBzaW5nbGVyID0gaWZlbHNlKAogICAgICBsYWJlbCA9PSAiTmV1cm9lbmRvY3JpbmUiLCAiTkUgKGxpa2VseSB0dW1vcikiLCAiTm9uLU5FIChsaWtlbHkgbm9ybWFsKSIpCiAgKSB8PgogIGRwbHlyOjpzZWxlY3QoLWxhYmVsKQogIApnb2kgPC0gdW5pcXVlKHRvcF9uYmF0bGFzX21hcmtlcnNfZGYkZW5zZW1ibF9nZW5lX2lkKQoKCmNvbXBhcmVfYW5ub3RhdGlvbnNfZXhwcl9kZiA8LSBzY3V0dGxlOjptYWtlUGVyQ2VsbERGKAogIG1lcmdlZF9zY2VbZ29pLCB1bmtub3duX2NvbnNlbnN1c19pZHNdLAogIGZlYXR1cmVzID0gZ29pLAogIGFzc2F5LnR5cGUgPSAibG9nY291bnRzIiwKICB1c2UuY29sZGF0YSA9ICJjZWxsX2lkIiwKICB1c2UuZGltcmVkID0gRkFMU0UKKSB8PgogIHRpZHlyOjpwaXZvdF9sb25nZXIoc3RhcnRzX3dpdGgoIkVOU0ciKSwgbmFtZXNfdG8gPSAiZW5zZW1ibF9nZW5lX2lkIiwgdmFsdWVzX3RvID0gImxvZ2NvdW50cyIpIHw+CiAgIyBmaWx0ZXIgdG8gb25seSBjZWxscyB3aXRoIGV4cHJlc3Npb24KICBkcGx5cjo6ZmlsdGVyKGxvZ2NvdW50cyA+IDApIHw+CiAgIyBqb2luIHRoZSBhbm5vdGF0aW9ucyBhbmQgbWFya2VyIGdlbmUgZGF0YSBmcmFtZTsgdXNlIGlubmVyIHNpbmNlIHdlJ3ZlIGZpbHRlcmVkIGFscmVhZHkKICBkcGx5cjo6aW5uZXJfam9pbih1bmtub3duX2NvbnNlbnN1c19jZWxsdHlwZV9kZiwgYnkgPSAiY2VsbF9pZCIpIHw+CiAgZHBseXI6OmxlZnRfam9pbih0b3BfbmJhdGxhc19tYXJrZXJzX2RmLCBieSA9ICJlbnNlbWJsX2dlbmVfaWQiLCByZWxhdGlvbnNoaXAgPSAibWFueS10by1tYW55IikgfD4KICAjIGF2ZXJhZ2UgYnkgbWFya2VyX2dlbmVfbGFiZWwKICBkcGx5cjo6Z3JvdXBfYnkoc2luZ2xlciwgbWFya2VyX2dlbmVfbGFiZWwsIGVuc2VtYmxfZ2VuZV9pZCkgfD4KICBkcGx5cjo6c3VtbWFyaXplKG1lYW5fZXhwID0gbWVhbihsb2djb3VudHMpKSB8PgogIGRwbHlyOjp1bmdyb3VwKCkgfD4KICBkcGx5cjo6bXV0YXRlKG1hcmtlcl9nZW5lX2xhYmVsID0gZmFjdG9yKG1hcmtlcl9nZW5lX2xhYmVsKSwgCiAgICAgICAgICAgICAgICBtYXJrZXJfZ2VuZV9sYWJlbCA9IGZvcmNhdHM6OmZjdF9yZWxldmVsKG1hcmtlcl9nZW5lX2xhYmVsLCBuYmF0bGFzX2Jhcl9vcmRlcikpCgpgYGAKCmBgYHtyLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDV9CmdncGxvdChjb21wYXJlX2Fubm90YXRpb25zX2V4cHJfZGYpICsgCiAgYWVzKHggPSBtYXJrZXJfZ2VuZV9sYWJlbCwgeSA9IG1lYW5fZXhwLCBjb2xvciA9IHNpbmdsZXIpICsgCiAgZ2dmb3JjZTo6Z2VvbV9zaW5hKHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjcpKSArIAogIGxhYnMoCiAgICB4ID0gIk5CQXRsYXMgbWFya2VyIGdlbmUgbGFiZWwiLCAKICAgIHkgPSAiTWVhbiBleHByZXNzaW9uIGFjcm9zcyBtYXJrZXIgZ2VuZXMiLAogICAgY29sb3IgPSAiU2luZ2xlUiBhbm5vdGF0aW9uIgogICkgKyAKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApCmBgYApUaGUgbGVmdC1tb3N0IGdlbmUgc2V0IG9uIHRoZSB4LWF4aXMgaXMgYE5ldXJvZW5kb2NyaW5lYCwgYW5kIG5vcm1hbCBjZWxsIHR5cGVzIGZvbGxvdy4KVGhpcyBwbG90IHN1Z2dlc3RzIHRoYXQgdGhlIG5vcm1hbCBjZWxscyBgU2luZ2xlUmAgaXMgcGlja2luZyB1cCBhcmUgbGlrZWx5IHRvIGJlIG5vcm1hbCBjZWxsczogbm9ybWFsIGdlbmUgZXhwcmVzc2lvbiBmb3Igbm9uLU5FIGNlbGxzIGlzIGhpZ2hlciBjb21wYXJlZCB0byBORSBjZWxscywgYW5kIHdlIHNlZSB0aGUgb3Bwb3NpdGUgdHJlbmQgZm9yIHRoZSBgTmV1cm9lbmRvY3JpbmVgIG1hcmtlciBnZW5lcy4KCgojIyBJbmZlcmVuY2VzIGZyb20gc21hbGxlciB2cy4gbGFyZ2VyIGxpYnJhcmllcyAKCkluIHRoaXMgc2VjdGlvbiwgd2UgYnJpZWZseSBsb29rIGF0IGFubm90YXRpb24gcmVzdWx0cyBmb3IgbGlicmFyaWVzIHdpdGggZmV3ZXIgdGhhbiAxMDAwIGNlbGxzIHdoaWNoIG1heSBiZSBsb3dlciBxdWFsaXR5IHRoYW4gb3RoZXIgbGlicmFyaWVzLgoKVGhvc2UgbGlicmFyaWVzIGFyZToKCmBgYHtyfQpsaWJyYXJpZXNfbG93X2NlbGxzIDwtIGNlbGx0eXBlX3JlY29kZWRfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGFubm90YXRpb25fdHlwZSA9PSAic2luZ2xlciIpIHw+CiAgZHBseXI6OnNlbGVjdCgtYW5ub3RhdGlvbl90eXBlKSB8PgogIGRwbHlyOjpjb3VudChsaWJyYXJ5X2lkLCBuYW1lID0gInRvdGFsX2NlbGxzIikgfD4KICBkcGx5cjo6ZmlsdGVyKHRvdGFsX2NlbGxzIDwgMTAwMCkgCmxpYnJhcmllc19sb3dfY2VsbHMKYGBgCgojIyMgRGlzdGlyYnV0aW9uIG9mIGNlbGwgdHlwZXMKCgpgYGB7ciwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA0fQpjZWxsdHlwZV9yZWNvZGVkX3dpZGVfZGYgfD4KICBkcGx5cjo6bXV0YXRlKAogICAgbGlicmFyeV9zaXplID0gaWZlbHNlKGxpYnJhcnlfaWQgJWluJSBsaWJyYXJpZXNfbG93X2NlbGxzJGxpYnJhcnlfaWQsICI8MTAwMCBjZWxscyIsICI+MTAwMCBjZWxscyIpCiAgKSB8PgogIGdncGxvdCgpICsgCiAgYWVzKHggPSBsaWJyYXJ5X2lkLCBmaWxsID0gc2luZ2xlcikgKyAKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJmaWxsIikgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSByZWNvZGVkX2NlbGx0eXBlX3BhbCkgKyAKICBmYWNldF93cmFwKHZhcnMobGlicmFyeV9zaXplKSwgc2NhbGVzID0gImZyZWVfeCIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKQogICkKYGBgCkFsdGhvdWdoIHRoZXJlIGFyZSBvbmx5IGZvdXIgbGlicmFyaWVzIHdpdGggPDEwMDAgY2VsbHMgc28gaXQgaXMgY2hhbGxlbmdpbmcgdG8gbWFrZSBkZWZpbml0aXZlIGNvbmNsdXNpb25zLCBpdCBkb2VzIG5vdCBzZWVtIGxpa2UgdGhlIG92ZXJhbGwgY2VsbCB0eXBlIGRpc3RyaWJ1dGlvbnMgYXJlIHZlcnkgZGlmZmVyZW50IGJldHdlZW4gc21hbGxlciBhbmQgbGFyZ2VyIGxpYnJhcmllcy4KCiMjIyBIZWF0bWFwCgpUaGlzIHNlY3Rpb24gc2hvd3MgYSBoZWF0bWFwIGNvbXBhcmluZyBhbm5vdGF0aW9ucyBmcm9tIHRoZXNlIGxpYnJhcmllcyBvbmx5IHRvIHRoZSBjb25zZW5zdXMgYW5ub3RhdGlvbnMuCgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDh9Cmxvd19jZWxsc19kZiA8LSBjZWxsdHlwZV9yZWNvZGVkX3dpZGVfZGYgfD4gCiAgZHBseXI6OmZpbHRlcihsaWJyYXJ5X2lkICVpbiUgbGlicmFyaWVzX2xvd19jZWxscyRsaWJyYXJ5X2lkKQoKbWFrZV9qYWNjYXJkX2hlYXRtYXAoCiAgbG93X2NlbGxzX2RmLAogICJjb25zZW5zdXNfdmFsaWRhdGlvbiIsCiAgInNpbmdsZXIiLAogICJDb25zZW5zdXMgdmFsaWRhdGlvbiBsYWJlbCIsCiAgIlNpbmdsZVIgTkJBdGxhcyBsYWJlbCIKKQpgYGAKClRoZSBoZWF0bWFwIGNvbXBhcmlzb24gdG8gY29uc2Vuc3VzIGNlbGwgdHlwZXMgZm9yIHRoZXNlIGxpYnJhcmllcyBsb29rcyByZWFzb25hYmxlOyBjb3JyZXNwb25kaW5nIGNlbGwgdHlwZXMgdGVuZCB0byBtYXRjaCB1cC4KTm90ZSB0aGF0IGJlY2F1c2UgdGhpcyBoZWF0bWFwIGluY2x1ZGVzIGZld2VyIGxpYnJhcmllcywgdGhlcmUgYXJlIGZld2VyIGNlbGwgdHlwZXMgdG8gZGlzcGxheSBvdmVyYWxsLgoKIyMjIFBlcmNlbnQgb2YgdW5jbGFzc2lmaWVkIGNlbGxzCgpOZXh0LCB3ZSdsbCBhc2sgd2hldGhlciB0aGUgcGVyY2VudCBvZiB1bmNsYXNzaWZpZWQgY2VsbHMgKGFrYSwgYHVua25vd25fc2luZ2xlcmApIGNhbiBiZSBleHBsYWluZWQgYnkgbGlicmFyeSBzaXplLgpCZWxvdywgd2UgcGxvdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdG90YWwgY2VsbHMgYW5kIGZyYWN0aW9uIG9mIGNlbGxzIHRoYXQgU2luZ2xlUiBmYWlsZWQgdG8gY29uZmlkZW50bHkgY2xhc3NpZnkuCgpgYGB7cn0KdW5rbm93bl9zaW5nbGVyX2RmIDwtIGNlbGx0eXBlX3JlY29kZWRfd2lkZV9kZiB8PgogIGRwbHlyOjpncm91cF9ieShsaWJyYXJ5X2lkKSB8PgogIGRwbHlyOjpzdW1tYXJpemUodW5rbm93bl9jZWxscz0gc3VtKHNpbmdsZXIgPT0gInVua25vd25fc2luZ2xlciIpKQoKcGVyY2VudF91bmNsYXNzaWZpZWRfZGYgPC0gY2VsbHR5cGVfcmVjb2RlZF93aWRlX2RmIHw+CiAgZHBseXI6OmFkZF9jb3VudChsaWJyYXJ5X2lkLCBuYW1lID0gInRvdGFsX2NlbGxzIikgfD4KICBkcGx5cjo6aW5uZXJfam9pbih1bmtub3duX3NpbmdsZXJfZGYsIGJ5ID0gImxpYnJhcnlfaWQiKSB8PgogIGRwbHlyOjpzZWxlY3QobGlicmFyeV9pZCwgdG90YWxfY2VsbHMsIHVua25vd25fY2VsbHMpIHw+CiAgZHBseXI6OmRpc3RpbmN0KCkgfD4KICBkcGx5cjo6bXV0YXRlKGZyYWNfc2luZ2xlcl91bmNsYXNzaWZpZWQgPSB1bmtub3duX2NlbGxzL3RvdGFsX2NlbGxzKQoKZ2dwbG90KHBlcmNlbnRfdW5jbGFzc2lmaWVkX2RmKSArIAogIGFlcyh4ID0gdG90YWxfY2VsbHMsIHkgPSBmcmFjX3NpbmdsZXJfdW5jbGFzc2lmaWVkKSArIAogIGdlb21fcG9pbnQoKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpCmBgYAoKVGhlcmUgZG9lc24ndCBhcHBlYXIgdG8gYmUgYSByZWxhdGlvbnNoaXAgaGVyZSwgYW5kIGZ1cnRoZXIgdGhlIHBlcmNlbnQgb2YgdW5jbGFzc2lmaWVkIGNlbGxzIGFyZSBhbGwgdW5kZXIgN1wlIHdpdGggbW9zdCBiZWxvdyBhcm91bmQgNFwlLiAKCiMjIEluZmVyZW5jZXMgZnJvbSBQRFggbGlicmFyaWVzCgpJbiB0aGlzIHNlY3Rpb24sIHdlIGJyaWVmbHkgbG9vayBhdCBhbm5vdGF0aW9uIHJlc3VsdHMgZm9yIGxpYnJhcmllcyBmcm9tIFBEWCBzYW1wbGVzLCB3aGljaCBtYXkgc2hvdyBkaXN0aW5jdCB0cmVuZHMuCkhlcmUgaXMgdGhlIGJyZWFrZG93biBvZiB0aGUgbnVtYmVyIG9mIFBEWCB2cyB0aXNzdWUgbGlicmFyaWVzOiAKCmBgYHtyfQojIFRoaXMgd2FzIGJlaW5nIGRldmVsb3BlZCB3aGlsZSB0aGlzIGJ1ZyB3YXMgcHJlc2VudCwgc28gd2UgaGF2ZSB0byBjaGVjayB0aGUgdHlwZSBmaXJzdAojIGh0dHBzOi8vZ2l0aHViLmNvbS9BbGV4c0xlbW9uYWRlL3NjcGNhVG9vbHMvaXNzdWVzLzMwMgpwZHhfY2xhc3MgPC0gY2xhc3MoY29sRGF0YShtZXJnZWRfc2NlKSRpc194ZW5vZ3JhZnQpCmlmIChwZHhfY2xhc3MgPT0gImNoYXJhY3RlciIpIHsKICBwZHhfdmFsIDwtICJUUlVFIgp9IGVsc2UgewogIHBkeF92YWwgPC0gVFJVRQp9CgpwZHhfbGlicmFyaWVzIDwtIGNvbERhdGEobWVyZ2VkX3NjZSkgfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICBkcGx5cjo6ZmlsdGVyKGlzX3hlbm9ncmFmdCA9PSBwZHhfdmFsKSB8PgogIGRwbHlyOjpwdWxsKGxpYnJhcnlfaWQpIHw+CiAgdW5pcXVlKCkKCmNlbGx0eXBlX3JlY29kZWRfd2lkZV9kZiA8LSBjZWxsdHlwZV9yZWNvZGVkX3dpZGVfZGYgfD4KICBkcGx5cjo6bXV0YXRlKHNhbXBsZV90eXBlID0gaWZlbHNlKGxpYnJhcnlfaWQgJWluJSBwZHhfbGlicmFyaWVzLCAicGR4IiwgInRpc3N1ZSIpKQoKY2VsbHR5cGVfcmVjb2RlZF93aWRlX2RmIHw+CiAgZHBseXI6OnNlbGVjdChsaWJyYXJ5X2lkLCBzYW1wbGVfdHlwZSkgfD4KICBkcGx5cjo6ZGlzdGluY3QoKSB8PgogIGRwbHlyOjpjb3VudChzYW1wbGVfdHlwZSkKYGBgCgojIyMgRGlzdHJpYnV0aW9uIG9mIGNlbGwgdHlwZXMKCldlJ2xsIGZpcnN0IGxvb2sgYXQgdGhlIGNlbGwgdHlwZSBkaXN0cmlidXRpb25zIGZvciBlYWNoIGxpYnJhcnkgYmV0d2VlbiBzYW1wbGUgdHlwZXM6CgpgYGB7ciwgZmlnLndpZHRoID0gMTQsIGZpZy5oZWlnaHQgPSA2fQpnZ3Bsb3QoY2VsbHR5cGVfcmVjb2RlZF93aWRlX2RmKSArIAogIGFlcyh4ID0gbGlicmFyeV9pZCwgZmlsbCA9IHNpbmdsZXIpICsgCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcmVjb2RlZF9jZWxsdHlwZV9wYWwpICsgCiAgZmFjZXRfd3JhcCh2YXJzKHNhbXBsZV90eXBlKSwgc2NhbGVzID0gImZyZWVfeCIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKQogICkKYGBgCgpQRFggc2FtcGxlcyBoYXZlLCBub3QgdW5leHBlY3RlZGx5LCBtdWNoIGxlc3MgY2VsbCB0eXBlIGRpdmVyc2l0eSBvdmVyYWxsIHdpdGggbW9zdCBjZWxscyBiZWluZyB0dW1vciBvciBzaW1pbGFyIGFuZCBpbW11bmUgY2VsbHMgcmFyZWx5IHByZXNlbnQuClRoaXMgaXMgY29uc2lzdGVudCB3aXRoIGV4cGVjdGF0aW9ucyBpbiBnZW5lcmFsIGZvciB0aGUgYmlvbG9neSBvZiBQRFgncy4KCgojIyMgSGVhdG1hcAoKVGhpcyBzZWN0aW9uIHNob3dzIGEgaGVhdG1hcCBjb21wYWluZyBTaW5nbGVSIGFubm90YXRpb25zIHRvIGNvbnNlbnN1cyBjZWxsIHR5cGVzLCBmb3IgUERYIHNhbXBsZXMgb25seS4KCmBgYHtyLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KbWFrZV9qYWNjYXJkX2hlYXRtYXAoCiAgY2VsbHR5cGVfcmVjb2RlZF93aWRlX2RmIHw+IGRwbHlyOjpmaWx0ZXIoc2FtcGxlX3R5cGUgPT0gInBkeCIpLAogICJjb25zZW5zdXNfdmFsaWRhdGlvbiIsCiAgInNpbmdsZXIiLAogICJDb25zZW5zdXMgdmFsaWRhdGlvbiBsYWJlbCIsCiAgIlNpbmdsZVIgTkJBdGxhcyBsYWJlbCIKKQpgYGAKVGhpcyBoZWF0bWFwIGNvbXBhcmlzb24gc2VlbXMgcmVhc29uYWJsZSwgYWdhaW4gdGFraW5nIGludG8gY29uc2lkZXJhdGlvbiB0aGF0IHRoZXJlIGFyZSByZWxhdGl2ZWx5IGZld2VyIGNlbGwgdHlwZXMgaW4gdGhlIFBEWCBzYW1wbGVzLCBhbmQgY29ycmVzcG9uZGluZyBjZWxsIHR5cGVzIHRlbmQgdG8gbWF0Y2ggdXAuCgoKCiMjIFNlc3Npb24gSW5mbwoKYGBge3Igc2Vzc2lvbiBpbmZvfQojIHJlY29yZCB0aGUgdmVyc2lvbnMgb2YgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhbmQgb3RoZXIgZW52aXJvbm1lbnQgaW5mb3JtYXRpb24Kc2Vzc2lvbkluZm8oKQpgYGAK